Java tutorial
/* * Copyright 2015 ZJNU ACM. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package cn.edu.zjnu.acm.judge.core; import cn.edu.zjnu.acm.judge.config.JudgeConfiguration; import cn.edu.zjnu.acm.judge.domain.Problem; import cn.edu.zjnu.acm.judge.domain.RunRecord; import cn.edu.zjnu.acm.judge.domain.Submission; import cn.edu.zjnu.acm.judge.mapper.ProblemMapper; import cn.edu.zjnu.acm.judge.mapper.SubmissionMapper; import cn.edu.zjnu.acm.judge.mapper.UserProblemMapper; import cn.edu.zjnu.acm.judge.service.JudgeServerService; import cn.edu.zjnu.acm.judge.service.LanguageService; import cn.edu.zjnu.acm.judge.util.ResultType; import com.github.zhanhb.judge.common.ExecuteResult; import com.github.zhanhb.judge.common.JudgeBridge; import com.github.zhanhb.judge.common.JudgeException; import com.github.zhanhb.judge.common.Options; import com.github.zhanhb.judge.common.SimpleValidator; import com.github.zhanhb.judge.common.Validator; import com.github.zhanhb.judge.win32.ProcessCreationHelper; import com.github.zhanhb.judge.win32.SpecialValidator; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.InterruptedIOException; import java.io.UncheckedIOException; import java.nio.charset.Charset; import java.nio.file.DirectoryStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.thymeleaf.util.StringUtils; /** * * @author zhanhb */ @Service @Slf4j public class Judger { private static String collectLines(Path path) throws IOException { Charset charset = Platform.getCharset(); String compileInfo; try (InputStream is = Files.newInputStream(path); InputStreamReader isr = new InputStreamReader(is, charset); BufferedReader br = new BufferedReader(isr)) { compileInfo = br.lines().collect(Collectors.joining("\n")); } return compileInfo.length() > 1000 ? compileInfo.substring(0, 997) + "..." : compileInfo; } @Autowired private SubmissionMapper submissionMapper; @Autowired private UserProblemMapper userProblemMapper; @Autowired private ProblemMapper problemMapper; @Autowired private JudgeServerService judgeServerService; @Autowired private JudgeConfiguration judgeConfiguration; @Autowired private LanguageService languageService; private ExecutorService executorService; @PostConstruct public void init() { final ThreadGroup group = new ThreadGroup("judge group"); final AtomicInteger countet = new AtomicInteger(); final ThreadFactory threadFactory = runnable -> new Thread(group, runnable, "judge thread " + countet.incrementAndGet()); executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), threadFactory); } @PreDestroy public void destroy() { executorService.shutdownNow(); } private void updateSubmissionStatus(RunRecord runRecord) { userProblemMapper.update(runRecord.getUserId(), runRecord.getProblemId()); userProblemMapper.updateUser(runRecord.getUserId()); userProblemMapper.updateProblem(runRecord.getProblemId()); } public void reJudge(long submissionId) throws InterruptedException, ExecutionException { Submission submission = submissionMapper.findOne(submissionId); if (submission == null) { return; } Problem problem = problemMapper.findOneNoI18n(submission.getProblem()); if (problem == null) { return; } Path dataPath = judgeConfiguration.getDataDirectory(problem.getId()); RunRecord runRecord = RunRecord.builder().submissionId(submission.getId()) .language(languageService.getAvailableLanguage(submission.getLanguage())) .problemId(submission.getProblem()).userId(submission.getUser()) .source(submissionMapper.findSourceById(submissionId)).dataPath(dataPath) .memoryLimit(problem.getMemoryLimit()).timeLimit(problem.getTimeLimit()).build(); executorService.submit(() -> judgeInternal(runRecord)).get(); } private boolean runProcess(RunRecord runRecord) throws IOException { Path dataPath = runRecord.getDataPath(); Objects.requireNonNull(dataPath, "dataPath"); Path specialFile = dataPath.resolve(JudgeConfiguration.VALIDATE_FILE_NAME); boolean isspecial = Files.exists(specialFile); if (!Files.isDirectory(dataPath)) { log.error("{} not exists", runRecord.getDataPath()); return false; } List<Path[]> files = new ArrayList<>(20); try (DirectoryStream<Path> listFiles = Files.newDirectoryStream(dataPath)) { log.debug("dataPath = {}", dataPath); for (Path inFile : listFiles) { String inFileName = inFile.getFileName().toString(); if (!inFileName.toLowerCase().endsWith(".in")) { continue; } Path outFile = dataPath.resolve(inFileName.substring(0, inFileName.length() - 3) + ".out"); if (!Files.exists(outFile)) { continue; } files.add(new Path[] { inFile, outFile });//, } } int casenum = files.size(); log.debug("casenum = {}", casenum); if (casenum == 0) { return false; } int accept = 0; //? ArrayList<String> details = new ArrayList<>(casenum << 2); long time = 0; // long memory = 0; // String command = runRecord.getLanguage().getExecuteCommand(); Path work = judgeConfiguration.getWorkDirectory(runRecord.getSubmissionId()); // command = !StringUtils.isEmptyOrWhitespace(command) ? command : work.resolve("Main." + runRecord.getLanguage().getExecutableExtension()).toString(); long extTime = runRecord.getLanguage().getExtTime(); long castTimeLimit = runRecord.getTimeLimit() * runRecord.getLanguage().getTimeFactor() + extTime; long extraMemory = runRecord.getLanguage().getExtMemory(); // long caseMemoryLimit = (runRecord.getMemoryLimit() + extraMemory) * 1024; Options[] optionses = new Options[casenum]; for (int cas = 0; cas < casenum; cas++) { Path[] entry = files.get(cas); Path in = entry[0]; Path standard = entry[1]; Path progOutput = work.resolve(standard.getFileName()); optionses[cas] = Options.builder().timeLimit(castTimeLimit) // time limit .memoryLimit(caseMemoryLimit) // memory in bytes .outputLimit(16 * 1024 * 1024) // 16M .command(command).workDirectory(work).inputFile(in).outputFile(progOutput) .standardOutput(standard).errFile(getNull(work)).build(); } String detailMessageStr = null; String scorePerCase = new DecimalFormat("0.#").format(100.0 / casenum); final Validator validator = isspecial ? new SpecialValidator(specialFile.toString(), work) : new SimpleValidator(); try { ExecuteResult[] ers = JudgeBridge.INSTANCE.judge(optionses, false, validator); for (ExecuteResult er : ers) { long tim1 = er.getTime() - extTime; tim1 = Math.max(0, tim1); long mem1 = er.getMemory() / 1024 - extraMemory; mem1 = Math.max(0, mem1); String message = er.getMessage(); int caseResult = getResultFromExecuteResult(er); time = Math.max(time, tim1); memory = Math.max(memory, mem1); log.debug("message = {}, time = {}, memory = {}", message, time, memory); details.add(String.valueOf(caseResult)); if (caseResult == 0) { details.add(scorePerCase); } else { details.add("0"); } details.add(String.valueOf(tim1)); details.add(String.valueOf(mem1)); if (caseResult == 0) { ++accept; } } } catch (JudgeException | RuntimeException | Error ex) { log.error("", ex); accept = ResultType.SYSTEM_ERROR; detailMessageStr = ex.getMessage(); } log.debug("{}", details); int score = accept >= 0 ? (int) Math.round(accept * 100.0 / casenum) : accept; if (score == 0 && accept != 0) { ++score; } else if (score == 100 && accept != casenum) { --score; } submissionMapper.updateResult(runRecord.getSubmissionId(), score, time, memory); submissionMapper.saveDetail(runRecord.getSubmissionId(), detailMessageStr != null ? detailMessageStr : details.stream().map(String::valueOf).collect(Collectors.joining(","))); updateSubmissionStatus(runRecord); return score == 100; } private boolean compile(RunRecord runRecord) throws IOException { String source = runRecord.getSource(); if (StringUtils.isEmptyOrWhitespace(source)) { return false; } Path work = runRecord.getWorkDirectory(); final String main = "Main"; Files.createDirectories(work); Path sourceFile = work.resolve(main + "." + runRecord.getLanguage().getSourceExtension()); //??? Files.copy(new ByteArrayInputStream(source.getBytes(Platform.getCharset())), sourceFile, StandardCopyOption.REPLACE_EXISTING); String compileCommand = runRecord.getLanguage().getCompileCommand(); log.debug("Compile Command: {}", compileCommand); // if (StringUtils.isEmptyOrWhitespace(compileCommand)) { return true; } assert compileCommand != null; // // VC++? // G++? Path compileInfo = work.resolve("compileinfo.txt"); Process process = ProcessCreationHelper.execute(new ProcessBuilder(compileCommand.split("\\s+")) .directory(work.toFile()).redirectOutput(compileInfo.toFile()).redirectErrorStream(true)::start); process.getInputStream().close(); try { process.waitFor(45, TimeUnit.SECONDS); } catch (InterruptedException ex) { throw new InterruptedIOException(); } //? String errorInfo; if (process.isAlive()) { process.destroy(); try { process.waitFor(); } catch (InterruptedException ex) { throw new InterruptedIOException(); } errorInfo = "Compile timeout\nOutput:\n" + collectLines(compileInfo); } else { errorInfo = collectLines(compileInfo); } log.debug("errorInfo = {}", errorInfo); Path executable = work.resolve(main + "." + runRecord.getLanguage().getExecutableExtension()); //?? log.debug("executable = {}", executable); boolean compileOK = Files.exists(executable); // if (!compileOK) { submissionMapper.updateResult(runRecord.getSubmissionId(), ResultType.COMPILE_ERROR, 0, 0); submissionMapper.saveCompileInfo(runRecord.getSubmissionId(), errorInfo); updateSubmissionStatus(runRecord); } return compileOK; } public Future<?> judge(RunRecord runRecord) { return executorService.submit(() -> judgeInternal(runRecord)); } private void judgeInternal(RunRecord runRecord) { Path workDirectory = judgeConfiguration.getWorkDirectory(runRecord.getSubmissionId()); runRecord.setWorkDirectory(workDirectory); try { if (compile(runRecord)) { runProcess(runRecord); } judgeServerService.delete(workDirectory); } catch (IOException ex) { throw new UncheckedIOException(ex); } } private int getResultFromExecuteResult(ExecuteResult er) { return er.getCode().getResult(); } private Path getNull(Path work) { return File.pathSeparatorChar == ';' ? work.resolve("NUL") : Paths.get("/dev/null"); } }