diff --git a/src/edu/csus/ecs/pc2/clics/API202306/CLICSJudgement.java b/src/edu/csus/ecs/pc2/clics/API202306/CLICSJudgement.java index 03cfe20cd..b27c51b86 100644 --- a/src/edu/csus/ecs/pc2/clics/API202306/CLICSJudgement.java +++ b/src/edu/csus/ecs/pc2/clics/API202306/CLICSJudgement.java @@ -3,17 +3,11 @@ import java.util.Calendar; import java.util.Date; -import java.util.HashSet; import java.util.Set; import java.util.logging.Level; -import com.fasterxml.jackson.annotation.JsonFilter; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ser.FilterProvider; -import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; -import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; import edu.csus.ecs.pc2.core.IInternalController; import edu.csus.ecs.pc2.core.Utilities; @@ -22,7 +16,6 @@ import edu.csus.ecs.pc2.core.model.JudgementRecord; import edu.csus.ecs.pc2.core.model.Run; import edu.csus.ecs.pc2.core.util.IJSONTool; -import edu.csus.ecs.pc2.services.core.JSONUtilities; /** * Contains the judgment for a submission (Accepted, Wrong Answer, etc). @@ -32,7 +25,6 @@ * */ @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonFilter("rtFilter") public class CLICSJudgement { @JsonProperty @@ -45,7 +37,7 @@ public class CLICSJudgement { private String judgement_type_id; @JsonProperty - private double score; + private Double score; @JsonProperty private String start_time; @@ -62,8 +54,6 @@ public class CLICSJudgement { @JsonProperty private double max_run_time; - private boolean isPointScoring = false; - /** * Fill in properties for a judgment description. * @@ -78,9 +68,6 @@ public CLICSJudgement(IInternalContest model, IInternalController controller, Ru id = submission.getElementId().toString(); submission_id = IJSONTool.getSubmissionId(submission); - // Remember this for serialization - isPointScoring = model.getContestInformation().isScoreboardTypeScore(); - Date startJudgeDate = submission.getJudgeStartDate(); if(startJudgeDate == null) { // Yikes! Using submission time since there is no judge start date... Something is amiss, but this is a last ditch guess @@ -143,33 +130,4 @@ public CLICSJudgement(IInternalContest model, IInternalController controller, Ru } // else not much to do here if no start date. } - - public String toJSON() { - Set exceptProps = new HashSet(); - - getExceptProps(exceptProps); - try { - ObjectMapper mapper = JSONUtilities.getObjectMapper(); - // for this judgment, create filter to omit inappropriate properties, - // 'score' in this case if not Point Scoring contest - SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.serializeAllExcept(exceptProps); - FilterProvider fp = new SimpleFilterProvider().addFilter("rtFilter", filter).setFailOnUnknownId(false); - mapper.setFilters(fp); - return mapper.writeValueAsString(this); - } catch (Exception e) { - return "Error creating JSON for judgment " + e.getMessage(); - } - } - - /** - * Get set of properties for which we do not want to serialize into JSON. - * This is so we don't serialize score for pass-fail contests - * - * @param exceptProps Set to fill in with property names to omit - */ - public void getExceptProps(Set exceptProps) { - if(!isPointScoring){ - exceptProps.add("score"); - } - } } diff --git a/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblem.java b/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblem.java index bf9602fe2..5a24b1f06 100644 --- a/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblem.java +++ b/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblem.java @@ -1,16 +1,9 @@ // Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. package edu.csus.ecs.pc2.clics.API202306; -import java.util.HashSet; -import java.util.Set; - -import com.fasterxml.jackson.annotation.JsonFilter; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ser.FilterProvider; -import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; -import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; import edu.csus.ecs.pc2.core.StringUtilities; import edu.csus.ecs.pc2.core.model.IInternalContest; @@ -28,7 +21,6 @@ * */ @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonFilter("rtFilter") public class CLICSProblem { @JsonProperty @@ -59,7 +51,7 @@ public class CLICSProblem { private int test_data_count; @JsonProperty - private double max_score; + private Double max_score; // The next two will be 'null' for now until we implement the new json CPF @JsonProperty("package") @@ -98,40 +90,34 @@ public CLICSProblem(IInternalContest model, Problem problem, int ordinal) { ProblemDataFiles problemDataFiles = model.getProblemDataFile(problem); if(problemDataFiles != null) { TestDataGroup [] testDataGroups = problemDataFiles.getJudgesDataGroups(); - // Really, there's only 1 top level TestDataGruop + // Really, there's only 1 top (root) level TestDataGroup which we have to find. Start at the + // first group and walk up the tree to the root (parent being null) if(testDataGroups != null && testDataGroups.length > 0) { - max_score = testDataGroups[0].getRangeMax(); + TestDataGroup tdg = testDataGroups[0]; + // Paranoia: this had better not be null. + if(tdg != null) { + TestDataGroup parentTdg = tdg.getParent(); + while(parentTdg != null) { + tdg = parentTdg; + parentTdg = tdg.getParent(); + } + // Departure from spec: if upper range is infinity, just leave it out (max_score will be null). + // Primarily for the Resolver as it doesn't want to see "infinity" as a value. + if(tdg.getRangeMax() != Double.POSITIVE_INFINITY) { + max_score = tdg.getRangeMax(); + } + } } } } } public String toJSON() { - Set exceptProps = new HashSet(); - - getExceptProps(exceptProps); try { ObjectMapper mapper = JSONUtilities.getObjectMapper(); - // for this problem, create filter to omit inappropriate properties, - // 'max_score' in this case if not Point Scoring contest - SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.serializeAllExcept(exceptProps); - FilterProvider fp = new SimpleFilterProvider().addFilter("rtFilter", filter).setFailOnUnknownId(false); - mapper.setFilters(fp); return mapper.writeValueAsString(this); } catch (Exception e) { return "Error creating JSON for CLICSProblem " + e.getMessage(); } } - - /** - * Get set of properties for which we do not want to serialize into JSON. - * This is so we don't serialize max_score for pass-fail contests - * - * @param exceptProps Set to fill in with property names to omit - */ - public void getExceptProps(Set exceptProps) { - if(!isPointScoring){ - exceptProps.add("max_score"); - } - } } diff --git a/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblemScore.java b/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblemScore.java index daaffe212..f4001544b 100644 --- a/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblemScore.java +++ b/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblemScore.java @@ -2,22 +2,14 @@ package edu.csus.ecs.pc2.clics.API202306; import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ser.FilterProvider; -import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; -import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; import edu.csus.ecs.pc2.core.StringUtilities; import edu.csus.ecs.pc2.core.Utilities; -import edu.csus.ecs.pc2.core.model.IInternalContest; import edu.csus.ecs.pc2.core.standings.ProblemSummaryInfo; -import edu.csus.ecs.pc2.services.core.JSONUtilities; /** * Contains information about the score a team received for a single problem. @@ -29,7 +21,6 @@ */ @JsonInclude(JsonInclude.Include.NON_NULL) - public class CLICSProblemScore { @JsonProperty @@ -45,13 +36,11 @@ public class CLICSProblemScore { private boolean solved; @JsonProperty - private double score; + private Double score; @JsonProperty private int time; - private boolean isPointScoring = false; - /** * Provide empty constructor for Jackson deserialization */ @@ -66,7 +55,7 @@ public CLICSProblemScore() { * @param probEleToShort hashmap for mapping problem elementid to shortname * @param versionInfo */ - public CLICSProblemScore(IInternalContest model, HashMap probEleToShort, ProblemSummaryInfo psi) { + public CLICSProblemScore(HashMap probEleToShort, ProblemSummaryInfo psi) { num_judged = Utilities.nullSafeToInt(psi.getAttempts(), 0); num_pending = Utilities.nullSafeToInt(psi.getIsPending(), 0); problem_id = psi.getProblemId(); @@ -88,38 +77,6 @@ public CLICSProblemScore(IInternalContest model, HashMap probEle } } } - if(model != null) { - isPointScoring = model.getContestInformation().isScoreboardTypeScore(); - } - } - - public String toJSON() { - Set exceptProps = new HashSet(); - - getExceptProps(exceptProps); - try { - ObjectMapper mapper = JSONUtilities.getObjectMapper(); - // for this problem's score, create filter to omit inappropriate properties, - // 'score' in this case if not Point Scoring contest - SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.serializeAllExcept(exceptProps); - FilterProvider fp = new SimpleFilterProvider().addFilter("rtFilter", filter).setFailOnUnknownId(false); - mapper.setFilters(fp); - return mapper.writeValueAsString(this); - } catch (Exception e) { - return "Error creating JSON for CLICSProblemScore " + e.getMessage(); - } - } - - /** - * Get set of properties for which we do not want to serialize into JSON. - * This is so we don't serialize score for pass-fail contests - * - * @param exceptProps Set to fill in with property names to omit - */ - public void getExceptProps(Set exceptProps) { - if(!isPointScoring){ - exceptProps.add("score"); - } } /** diff --git a/src/edu/csus/ecs/pc2/clics/API202306/CLICSScore.java b/src/edu/csus/ecs/pc2/clics/API202306/CLICSScore.java index 4e9525f53..ba278ca43 100644 --- a/src/edu/csus/ecs/pc2/clics/API202306/CLICSScore.java +++ b/src/edu/csus/ecs/pc2/clics/API202306/CLICSScore.java @@ -1,22 +1,12 @@ // Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. package edu.csus.ecs.pc2.clics.API202306; -import java.util.HashSet; -import java.util.Set; - import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonFilter; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ser.FilterProvider; -import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; -import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; import edu.csus.ecs.pc2.core.Utilities; -import edu.csus.ecs.pc2.core.model.IInternalContest; import edu.csus.ecs.pc2.core.standings.TeamStanding; -import edu.csus.ecs.pc2.services.core.JSONUtilities; /** * Contains information about the score for a team on the scoreboard. @@ -25,7 +15,6 @@ * */ @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonFilter("rtFilter") public class CLICSScore { @JsonProperty @@ -35,13 +24,11 @@ public class CLICSScore { private int total_time; @JsonProperty - private double score; + private Double score; @JsonProperty private int time; - private boolean isPointScoring = false; - /** * Provide empty constructor for Jackson deserialization */ @@ -56,7 +43,7 @@ public CLICSScore() { * @param teamStanding The team's scoring information * @throws NumberFormatException if bad scores are in the standings */ - public CLICSScore(IInternalContest model, TeamStanding teamStanding) { + public CLICSScore(TeamStanding teamStanding) { num_solved = Utilities.nullSafeToInt(teamStanding.getSolved(), 0); total_time = Utilities.nullSafeToInt(teamStanding.getPoints(), 0); if(num_solved > 0) { @@ -64,39 +51,8 @@ public CLICSScore(IInternalContest model, TeamStanding teamStanding) { time = Integer.parseInt(teamStanding.getLastSolved()); score = Double.parseDouble(teamStanding.getScore()); } - if(model != null) { - isPointScoring = model.getContestInformation().isScoreboardTypeScore(); - } - } - - public String toJSON() { - Set exceptProps = new HashSet(); - - getExceptProps(exceptProps); - try { - ObjectMapper mapper = JSONUtilities.getObjectMapper(); - // for this team score, create filter to omit inappropriate properties, - // 'score' in this case if not Point Scoring contest - SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.serializeAllExcept(exceptProps); - FilterProvider fp = new SimpleFilterProvider().addFilter("rtFilter", filter).setFailOnUnknownId(false); - mapper.setFilters(fp); - return mapper.writeValueAsString(this); - } catch (Exception e) { - return "Error creating JSON for CLICSScore " + e.getMessage(); - } } - /** - * Get set of properties for which we do not want to serialize into JSON. - * This is so we don't serialize score for pass-fail contests - * - * @param exceptProps Set to fill in with property names to omit - */ - public void getExceptProps(Set exceptProps) { - if(!isPointScoring){ - exceptProps.add("score"); - } - } public int getNum_solved() { return num_solved; @@ -113,7 +69,6 @@ public int getTime() { /** * @return the score */ - public Double getScore() { return score; } diff --git a/src/edu/csus/ecs/pc2/clics/API202306/CLICSScoreboard.java b/src/edu/csus/ecs/pc2/clics/API202306/CLICSScoreboard.java index c47e70618..9b7e23787 100644 --- a/src/edu/csus/ecs/pc2/clics/API202306/CLICSScoreboard.java +++ b/src/edu/csus/ecs/pc2/clics/API202306/CLICSScoreboard.java @@ -117,7 +117,7 @@ public CLICSScoreboard(IInternalContest model, Group group, Integer division) t List standings = contestStandings.getTeamStandings(); if(standings != null) { for (TeamStanding teamStanding : standings) { - rowsArray.add(new CLICSScoreboardRow(model, probEleToShortName, teamStanding)); + rowsArray.add(new CLICSScoreboardRow(probEleToShortName, teamStanding)); } rows = rowsArray.toArray(new CLICSScoreboardRow[0]); } else { diff --git a/src/edu/csus/ecs/pc2/clics/API202306/CLICSScoreboardRow.java b/src/edu/csus/ecs/pc2/clics/API202306/CLICSScoreboardRow.java index aff16d992..cedac4218 100644 --- a/src/edu/csus/ecs/pc2/clics/API202306/CLICSScoreboardRow.java +++ b/src/edu/csus/ecs/pc2/clics/API202306/CLICSScoreboardRow.java @@ -11,7 +11,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import edu.csus.ecs.pc2.core.Utilities; -import edu.csus.ecs.pc2.core.model.IInternalContest; import edu.csus.ecs.pc2.core.standings.ProblemSummaryInfo; import edu.csus.ecs.pc2.core.standings.TeamStanding; @@ -51,15 +50,15 @@ public CLICSScoreboardRow() { * @param probEleToShortName hashmap for mapping problem elementid to shortname * @param teamStanding xml representation of the standings for a team */ - public CLICSScoreboardRow(IInternalContest model, HashMap probEleToShortName, TeamStanding teamStanding) { + public CLICSScoreboardRow(HashMap probEleToShortName, TeamStanding teamStanding) { team_id = teamStanding.getTeamId(); rank = Utilities.nullSafeToInt(teamStanding.getRank(), 0); - score = new CLICSScore(model, teamStanding); + score = new CLICSScore(teamStanding); ArrayList pslist = new ArrayList(); for( ProblemSummaryInfo psi : teamStanding.getProblemSummaryInfos()) { - pslist.add(new CLICSProblemScore(model, probEleToShortName, psi)); + pslist.add(new CLICSProblemScore(probEleToShortName, psi)); } problems = pslist.toArray(new CLICSProblemScore[0]); } diff --git a/src/edu/csus/ecs/pc2/clics/API202306/SubmissionService.java b/src/edu/csus/ecs/pc2/clics/API202306/SubmissionService.java index d07b64f29..a3e4596ca 100644 --- a/src/edu/csus/ecs/pc2/clics/API202306/SubmissionService.java +++ b/src/edu/csus/ecs/pc2/clics/API202306/SubmissionService.java @@ -485,9 +485,9 @@ public synchronized Response addNewSubmission(@Context HttpServletRequest servle return Response.status(Status.BAD_REQUEST).entity("invalid json supplied").build(); } - // These next three are for admin users only + // These next two are for admin users only long overrideTimeMS = -1; - long overrideSubmissionID = -1; + long overrideSubmissionID = 0; Log log = controller.getLog(); String user = sc.getUserPrincipal().getName(); @@ -570,7 +570,7 @@ public synchronized Response addNewSubmission(@Context HttpServletRequest servle } overrideSubmissionID = Utilities.stringToLong(sub.getId()); if(overrideSubmissionID < 0) { - overrideSubmissionID = -1; + overrideSubmissionID = 0; } } @@ -606,9 +606,15 @@ public synchronized Response addNewSubmission(@Context HttpServletRequest servle if("".equals(fileName)) { return Response.status(Response.Status.BAD_REQUEST).entity("no file name specified").build(); } + // allow contestant submission of a zero length file. This will generate a CE (hopefully). + // if the following code is uncommented, the submission is not made and a 400 is returned to the submitter. + // it appears that other CCS's allow zero length submissions. *sigh* -- JB String fileData = file.getData(); if(fileData == null || fileData.length() == 0) { - return Response.status(Response.Status.BAD_REQUEST).entity("no file data specified for " + fileName).build(); + // nice to put it in the log in case any questions come up. + log.info(user + " POSTing empty source submission on behalf of team " + team_id); + +// return Response.status(Response.Status.BAD_REQUEST).entity("no file data specified for " + fileName).build(); } IFile iFile = new IFileImpl(file.getFilename(), fileData); srcFiles.add(iFile); diff --git a/src/edu/csus/ecs/pc2/core/InternalController.java b/src/edu/csus/ecs/pc2/core/InternalController.java index abfdc2a35..2f35f9120 100644 --- a/src/edu/csus/ecs/pc2/core/InternalController.java +++ b/src/edu/csus/ecs/pc2/core/InternalController.java @@ -610,20 +610,23 @@ public void submitJudgeRun(Problem problem, Language language, SerializedFile ma //determine whether to apply the "current throttling strategy" to this submission // (i.e., whether to accept the run for submission to the PC2 Server) boolean accept = true; - Account submitterAccount = contest.getAccount(contest.getClientId()); - - if (submitterAccount.isTeam()) { - //Determine the throttling strategy to be applied to team submissions. - //The following shows several alternative strategy selections, with only one being enabled. - //A preferable extension would be to allow external (e.g. run-time) selection of the desired strategy, - // chosen from among a list of available strategies and specified by, e.g. a YAML file or an interactive - // GUI (such as the PC2 Admin) -// IThrottleStrategy strategy = new AcceptAllStrategy(); -// IThrottleStrategy strategy = new RejectAllStrategy(); - IThrottleStrategy strategy = new MaxSubmissionsPerMinuteStrategy(contest,6); -// IThrottleStrategy strategy = new MaxSubmissionsPerMinuteStrategy(contest,MaxSubmissionsPerMinuteStrategy.DEFAULT_MAX_SUBMISSIONS_PER_MINUTE); -// IThrottleStrategy strategy = new MaxSubmissionsPerMinuteStrategy(contest); //uses DEFAULT_MAX_SUBMISSIONS_PER_MINUTE; same as prev line - accept = strategy.accept(run); + + if(contest.getContestInformation().isSubmissionThrottling()) { + Account submitterAccount = contest.getAccount(contest.getClientId()); + + if (submitterAccount.isTeam()) { + //Determine the throttling strategy to be applied to team submissions. + //The following shows several alternative strategy selections, with only one being enabled. + //A preferable extension would be to allow external (e.g. run-time) selection of the desired strategy, + // chosen from among a list of available strategies and specified by, e.g. a YAML file or an interactive + // GUI (such as the PC2 Admin) + // IThrottleStrategy strategy = new AcceptAllStrategy(); + // IThrottleStrategy strategy = new RejectAllStrategy(); + IThrottleStrategy strategy = new MaxSubmissionsPerMinuteStrategy(contest,6); + // IThrottleStrategy strategy = new MaxSubmissionsPerMinuteStrategy(contest,MaxSubmissionsPerMinuteStrategy.DEFAULT_MAX_SUBMISSIONS_PER_MINUTE); + // IThrottleStrategy strategy = new MaxSubmissionsPerMinuteStrategy(contest); //uses DEFAULT_MAX_SUBMISSIONS_PER_MINUTE; same as prev line + accept = strategy.accept(run); + } } Packet packet ; @@ -5008,21 +5011,24 @@ public void submitRun(ClientId submitter, Problem problem, Language language, St // There should probably be a "bypass" flag that can be set via config file, or // a separate user created for receiving CLICS API requests. boolean accept = true; - // use the submitter account, not this client. This client is doing a proxy submit for a team. - Account submitterAccount = contest.getAccount(submitter); - - if (submitterAccount.isTeam()) { - //Determine the throttling strategy to be applied to team submissions. - //The following shows several alternative strategy selections, with only one being enabled. - //A preferable extension would be to allow external (e.g. run-time) selection of the desired strategy, - // chosen from among a list of available strategies and specified by, e.g. a YAML file or an interactive - // GUI (such as the PC2 Admin) -// IThrottleStrategy strategy = new AcceptAllStrategy(); -// IThrottleStrategy strategy = new RejectAllStrategy(); - IThrottleStrategy strategy = new MaxSubmissionsPerMinuteStrategy(contest, 6); -// IThrottleStrategy strategy = new MaxSubmissionsPerMinuteStrategy(contest,MaxSubmissionsPerMinuteStrategy.DEFAULT_MAX_SUBMISSIONS_PER_MINUTE); - - accept = strategy.accept(run); + + if(contest.getContestInformation().isSubmissionThrottling()) { + // use the submitter account, not this client. This client is doing a proxy submit for a team. + Account submitterAccount = contest.getAccount(submitter); + + if (submitterAccount.isTeam()) { + //Determine the throttling strategy to be applied to team submissions. + //The following shows several alternative strategy selections, with only one being enabled. + //A preferable extension would be to allow external (e.g. run-time) selection of the desired strategy, + // chosen from among a list of available strategies and specified by, e.g. a YAML file or an interactive + // GUI (such as the PC2 Admin) + // IThrottleStrategy strategy = new AcceptAllStrategy(); + // IThrottleStrategy strategy = new RejectAllStrategy(); + IThrottleStrategy strategy = new MaxSubmissionsPerMinuteStrategy(contest, 6); + // IThrottleStrategy strategy = new MaxSubmissionsPerMinuteStrategy(contest,MaxSubmissionsPerMinuteStrategy.DEFAULT_MAX_SUBMISSIONS_PER_MINUTE); + + accept = strategy.accept(run); + } } if (accept) { diff --git a/src/edu/csus/ecs/pc2/core/execute/Executable.java b/src/edu/csus/ecs/pc2/core/execute/Executable.java index 94573fe53..7849b913b 100644 --- a/src/edu/csus/ecs/pc2/core/execute/Executable.java +++ b/src/edu/csus/ecs/pc2/core/execute/Executable.java @@ -862,6 +862,9 @@ private void writeGraderResultsToFile(Run run, String graderResultFileName) { private String getPointScoringRunResult(Run run) { //"getTestDataGroupResult()" == "recurse(tdg)" + // This may not be sufficient. We want to be sure we start at the root. testcases[0] should be + // "sample" and its parent should be root. We should probably put a loop here to walk back up until + // getParent() == null, then use that TDG. But, I think this SHOULD be ok? -- JB String runResult = getTestDataGroupResults(run.getRunTestCases()[0].getTestDataGroup().getParent()); return runResult; } @@ -882,6 +885,7 @@ private String getTestDataGroupResults(TestDataGroup tdg) { //a list of the test case results associated with the specified test data group ArrayList testCaseResultList = new ArrayList() ; + boolean breakOnReject = tdg.isOnRejectBreak(); //get the results for the test cases directly declared in the test data group and add them to the list ArrayList groupTestCaseResults = getGroupTestCaseResults(tdg); @@ -889,14 +893,30 @@ private String getTestDataGroupResults(TestDataGroup tdg) { testCaseResultList.add(testCaseResult); } - //recursively get the results for test cases declared as children of the specified test data group and add them to the list - for (TestDataGroup child : tdg.getTestDataGroups()) { - String childResult = getTestDataGroupResults(child); - testCaseResultList.add(childResult); + // if we are not breaking on reject, or there are no test cases in this groups level, or, the last case on the list + // is accepted, then, we have to get the results of each sub group and add to the list. + // Note that getGroupTestCaseResults() will return on the first failed case if on_reject = break, and, the last + // result in the list will be the failed result (non-AC) + if(!breakOnReject || testCaseResultList.isEmpty() || + testCaseResultList.get(testCaseResultList.size()-1).split("\\s+")[0].equalsIgnoreCase(CLICS_JUDGEMENT_ACRONYM.AC.toString())){ + + //recursively get the results for test cases declared as children of the specified test data group and add them to the list + for (TestDataGroup child : tdg.getTestDataGroups()) { + String childResult = getTestDataGroupResults(child); + if(childResult == null) { + log.log(Log.WARNING, "Grader childResult was null for test data group: '" + child.getGroupName() + "' in group '" + tdg.getGroupName() + "'"); + return(null); + } + testCaseResultList.add(childResult); + // If we are supposed to break on reject, and any of the sub groups return a non-AC then stop adding subgroups and proceed to grading. + if(breakOnReject && !childResult.split("\\s+")[0].equalsIgnoreCase(CLICS_JUDGEMENT_ACRONYM.AC.toString())) { + break; + } + } } //we've recursed to the lowest level in the test case tree; create a Grader to get a Result (acronym and score) for this level - LegacyGrader grader = new LegacyGrader(); + LegacyGrader grader = new LegacyGrader(prefixExecuteDirname("graderLog-" + tdg.getGroupName().replace(File.separator, "_") + ".txt")); //set the arguments for the grader based on the grader flags in the currently specified TestDataGroup. //TODO: it seems like there SHOULD be separate "scoringMode" and "verdictMode" attributes defined in a TestDataGroup -- , @@ -956,6 +976,7 @@ private String getTestDataGroupResults(TestDataGroup tdg) { private ArrayList getGroupTestCaseResults(TestDataGroup tdg) { //a list of test cases declared directly in the specfied TestDataGroup ArrayList tdgTestCaseResults = new ArrayList(); + boolean breakOnReject = tdg.isOnRejectBreak(); //check every test case in the run for (RunTestCase testCase : run.getRunTestCases()) { @@ -964,6 +985,10 @@ private ArrayList getGroupTestCaseResults(TestDataGroup tdg) { //yes, the test case belongs to the test data group; add its result to the list String testCaseResult = testCase.getJudgementAcronym().toString() + " " + testCase.getScore(); tdgTestCaseResults.add(testCaseResult); + // Stop adding to list on first failed case, if that's what is wanted. + if(breakOnReject && !testCase.isPassed()) { + break; + } } } return tdgTestCaseResults; diff --git a/src/edu/csus/ecs/pc2/core/model/ContestInformation.java b/src/edu/csus/ecs/pc2/core/model/ContestInformation.java index 71d226d52..6885dbdb4 100644 --- a/src/edu/csus/ecs/pc2/core/model/ContestInformation.java +++ b/src/edu/csus/ecs/pc2/core/model/ContestInformation.java @@ -237,6 +237,11 @@ public String getType() { */ RemoteCCSInformation remoteCCSInfo[] = null; + /** + * Submission Throttling + */ + private boolean submissionThrottling = true; + /** * Returns the date/time when the contest is scheduled (intended) to start. * This value is null if no scheduled start time has been set, @@ -1076,4 +1081,12 @@ public RemoteCCSInformation getRemoteCCSInfo(String account) { } return(remoteInfo); } + public boolean isSubmissionThrottling() { + return submissionThrottling; + } + + public void setSubmissionThrottling(boolean submissionThrottling) { + this.submissionThrottling = submissionThrottling; + } + } diff --git a/src/edu/csus/ecs/pc2/core/model/ProblemDataFiles.java b/src/edu/csus/ecs/pc2/core/model/ProblemDataFiles.java index e150c92a6..63e859573 100644 --- a/src/edu/csus/ecs/pc2/core/model/ProblemDataFiles.java +++ b/src/edu/csus/ecs/pc2/core/model/ProblemDataFiles.java @@ -1,4 +1,4 @@ -// Copyright (C) 1989-2019 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. +// Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. package edu.csus.ecs.pc2.core.model; import java.io.File; diff --git a/src/edu/csus/ecs/pc2/core/scoring/FinalsStandingsPointScoringRecordComparator.java b/src/edu/csus/ecs/pc2/core/scoring/FinalsStandingsPointScoringRecordComparator.java new file mode 100644 index 000000000..52d06ba9c --- /dev/null +++ b/src/edu/csus/ecs/pc2/core/scoring/FinalsStandingsPointScoringRecordComparator.java @@ -0,0 +1,110 @@ +// Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. +package edu.csus.ecs.pc2.core.scoring; + +import java.io.Serializable; +import java.util.Comparator; + +import edu.csus.ecs.pc2.core.list.AccountList; +import edu.csus.ecs.pc2.core.list.AccountNameCaseComparator; +import edu.csus.ecs.pc2.core.model.Account; + +/** + * Sorts StandingsRecord according to the ACM-ICPC World Finals Rules (as of 2025) for point scoring contests + * + * @author John Buck + * @version $Id$ + */ + +// $HeadURL$ +public class FinalsStandingsPointScoringRecordComparator implements Serializable, Comparator { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private AccountNameCaseComparator accountNameCaseComparator = new AccountNameCaseComparator(); + + private AccountList cachedAccountList; + + /** + * Compares its two arguments for order. Returns a negative integer, zero, or a positive integer as the first argument is less + * than, equal to, or greater than the second. + *

+ * + * The implementor must ensure that sgn(compare(x, y)) == + * -sgn(compare(y, x)) for all x and y. + * (This implies that compare(x, y) must throw an exception if and only if compare(y, x) throws an + * exception.) + *

+ * + * The implementor must also ensure that the relation is transitive: + * ((compare(x, y)>0) && (compare(y, z)>0)) implies compare(x, z)>0. + *

+ * + * Finally, the implementer must ensure that compare(x, y)==0 implies that + * sgn(compare(x, z))==sgn(compare(y, z)) for all z. + *

+ * + * It is generally the case, but not strictly required that (compare(x, y)==0) == (x.equals(y)). Generally + * speaking, any comparator that violates this condition should clearly indicate this fact. The recommended language is "Note: + * this comparator imposes orderings that are inconsistent with equals." + * + * @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the + * second. + * @throws ClassCastException + * if the arguments' types prevent them from being compared by this Comparator. + */ + @Override + public int compare(StandingsRecord o1, StandingsRecord o2) { + int status = 0; + double aScore, bScore; + long aTime, bTime; + int aHash, bHash; + String aName, bName; + + StandingsRecord teamA = o1; + StandingsRecord teamB = o2; + aScore = teamA.getScore(); + aTime = teamA.getLastSolved(); + Account accountA = cachedAccountList.getAccount(teamA.getClientId()); + aName = accountA.getDisplayName(); + aHash = teamA.getClientId().hashCode(); + bScore = teamB.getScore(); + bTime = teamB.getLastSolved(); + Account accountB = cachedAccountList.getAccount(teamB.getClientId()); + bName = accountB.getDisplayName(); + bHash = teamB.getClientId().hashCode(); + + // + // Primary Sort = score (high to low) + // Secondary Sort = time (low to high) + // Third Sort = teamName (low to high) + // Fourth Sort = clientId (low to high) + + int nameComparison = accountNameCaseComparator.compare(aName, bName); + if ((bScore == aScore) && (bTime == aTime) && (nameComparison == 0) + && (bHash == aHash)) { + status = 0; // elements equal, this shouldn't happen, Tammy... + } else { + if ((bScore > aScore) + || ((bScore == aScore) && (bTime < aTime)) + || ((bScore == aScore) && (bTime == aTime) && (nameComparison > 0)) + || ((bScore == aScore) && (bTime == aTime) + && (nameComparison == 0) && (bHash < aHash))) { + status = 1; // a considered greater then b + } else { + status = -1; // a considered less then b + } + } + return status; + } + + /** + * @param accountList + * The cachedAccountList to set. + */ + public void setCachedAccountList(AccountList accountList) { + this.cachedAccountList = accountList; + } +} diff --git a/src/edu/csus/ecs/pc2/core/scoring/NewScoringAlgorithm.java b/src/edu/csus/ecs/pc2/core/scoring/NewScoringAlgorithm.java index 8b28b6bc5..d73413288 100644 --- a/src/edu/csus/ecs/pc2/core/scoring/NewScoringAlgorithm.java +++ b/src/edu/csus/ecs/pc2/core/scoring/NewScoringAlgorithm.java @@ -2,8 +2,10 @@ package edu.csus.ecs.pc2.core.scoring; import java.io.IOException; +import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -29,6 +31,7 @@ import edu.csus.ecs.pc2.core.model.Group; import edu.csus.ecs.pc2.core.model.IInternalContest; import edu.csus.ecs.pc2.core.model.Judgement; +import edu.csus.ecs.pc2.core.model.JudgementRecord; import edu.csus.ecs.pc2.core.model.Problem; import edu.csus.ecs.pc2.core.model.Run; import edu.csus.ecs.pc2.core.model.RunUtilities; @@ -61,10 +64,10 @@ public class NewScoringAlgorithm extends Plugin implements INewScoringAlgorithm private boolean respectEOC = false; - private DefaultStandingsRecordComparator comparator = new DefaultStandingsRecordComparator(); - private PermissionList permissionList = new PermissionList(); + private boolean isPointScoring = false; + /** * Return a list of regional winners. * @@ -182,6 +185,7 @@ public StandingsRecord[] getStandingsRecords(IInternalContest contest, Integer d } setContest(contest); + isPointScoring = contest.getContestInformation().isScoreboardTypeScore(); /* * Get all the teams, then create a new vector of only those teams shown on the @@ -213,7 +217,22 @@ public StandingsRecord[] getStandingsRecords(IInternalContest contest, Integer d for (Account account : accounts) { accountList.add(account); } - comparator.setCachedAccountList(accountList); + Comparator comparator; + + // Note: each of DefaultStandingsRecordComparator and DefaultPointScoringStandingsRecordComparator + // implements the java.util.Comparator interface. However, we need additional information in the + // comparator for StandingsRecord, namely, the accountList (for looking up names). This is why we + // instantiate each object separately, set the accountlist then assign to src. I suppose we could create + // another interface that extends java.util.Comparator without our method to set the cached account list. + if(!isPointScoring) { + DefaultStandingsRecordComparator srcPassFailRecordComparator = new DefaultStandingsRecordComparator(); + srcPassFailRecordComparator.setCachedAccountList(accountList); + comparator = srcPassFailRecordComparator; + } else { + DefaultPointScoringStandingsRecordComparator srcPointScoringRecordComparator = new DefaultPointScoringStandingsRecordComparator(); + srcPointScoringRecordComparator.setCachedAccountList(accountList); + comparator = srcPointScoringRecordComparator; + } if (runs == null) { runs = ScoreboardUtilities.getGroupFilteredRuns(getContest(), wantedGroups); @@ -357,14 +376,23 @@ public String getStandings(IInternalContest contest, Run[] runs, Integer divisio */ boolean isTied(StandingsRecord standingsRecord, StandingsRecord standingsRecord2) { - if (standingsRecord2.getNumberSolved() != standingsRecord.getNumberSolved()) { - return false; - } - if (standingsRecord2.getPenaltyPoints() != standingsRecord.getPenaltyPoints()) { - return false; - } - if (standingsRecord2.getLastSolved() != standingsRecord.getLastSolved()) { - return false; + if(isPointScoring) { + if(standingsRecord.getScore() != standingsRecord2.getScore()) { + return false; + } + if(standingsRecord.getLastSolved() != standingsRecord2.getLastSolved()) { + return false; + } + } else { + if (standingsRecord2.getNumberSolved() != standingsRecord.getNumberSolved()) { + return false; + } + if (standingsRecord2.getPenaltyPoints() != standingsRecord.getPenaltyPoints()) { + return false; + } + if (standingsRecord2.getLastSolved() != standingsRecord.getLastSolved()) { + return false; + } } return true; } @@ -482,15 +510,18 @@ private GrandTotals addProblemSummaryMememento(IMemento summaryMememento, Standi ProblemSummaryInfo problemSummaryInfo = summaryRow.get(i + 1); if (problemSummaryInfo != null) { - long solveTime = problemSummaryInfo.getSolutionTime(); - if (fastestSolved[i] == 0 || solveTime < fastestSolved[i]) { - fastestSolved[i] = solveTime; - } - if (solveTime > lastSolutionTime[i]) { - lastSolutionTime[i] = solveTime; - } - if (problemSummaryInfo.isSolved()) { - numberSolved[i]++; + // Check if solved, even somewhat solved for PS + if(!isPointScoring || problemSummaryInfo.getScore() == 0.0) { + long solveTime = problemSummaryInfo.getSolutionTime(); + if (fastestSolved[i] == 0 || solveTime < fastestSolved[i]) { + fastestSolved[i] = solveTime; + } + if (solveTime > lastSolutionTime[i]) { + lastSolutionTime[i] = solveTime; + } + if (problemSummaryInfo.isSolved()) { + numberSolved[i]++; + } } numberAttempts[i] += problemSummaryInfo.getNumberSubmitted(); } @@ -503,6 +534,8 @@ private GrandTotals addProblemSummaryMememento(IMemento summaryMememento, Standi IMemento problemMemento = summaryMememento.createChild("problem"); problemMemento.putInteger("id", id); problemMemento.putString("title", problems[i].getDisplayName()); + problemMemento.putString("color", problems[i].getColorName()); + problemMemento.putString("rgb", problems[i].getColorRGB()); problemMemento.putLong("attempts", numberAttempts[i]); grandTotals.incrementTotalAttempts(numberAttempts[i]); @@ -533,7 +566,8 @@ private IMemento addTeamMemento(IMemento mementoRoot, IInternalContest contest, IMemento standingsRecordMemento = mementoRoot.createChild("teamStanding"); - String teamVarDisplayString = contest.getContestInformation().getTeamScoreboardDisplayFormat(); + ContestInformation contestInformation = contest.getContestInformation(); + String teamVarDisplayString = contestInformation.getTeamScoreboardDisplayFormat(); Account account = contest.getAccount(standingsRecord.getClientId()); HashSet groups = account.getGroupIds(); @@ -541,7 +575,12 @@ private IMemento addTeamMemento(IMemento mementoRoot, IInternalContest contest, standingsRecordMemento.putLong("firstSolved", standingsRecord.getFirstSolved()); standingsRecordMemento.putLong("lastSolved", standingsRecord.getLastSolved()); - standingsRecordMemento.putLong("points", standingsRecord.getPenaltyPoints()); + if(contestInformation.isScoreboardTypeScore()) { + DecimalFormat df = new DecimalFormat("0.0###"); + standingsRecordMemento.putString("score", df.format(standingsRecord.getScore())); + } else { + standingsRecordMemento.putLong("points", standingsRecord.getPenaltyPoints()); + } standingsRecordMemento.putInteger("solved", standingsRecord.getNumberSolved()); standingsRecordMemento.putInteger("rank", standingsRecord.getRankNumber()); standingsRecordMemento.putInteger("index", indexNumber); @@ -633,7 +672,11 @@ private StandingsRecord[] computeStandingStandingsRecords(Run[] runs, Account[] // old ProblemScoreRecord problemScoreRecord = new ProblemScoreRecord(teamProblemRuns, problem, properties); ProblemScoreRecord problemScoreRecord = createProblemScoreRecord(teamProblemRuns, problem, properties); - standingsRecord.setPenaltyPoints(standingsRecord.getPenaltyPoints() + problemScoreRecord.getPoints()); + if(isPointScoring) { + standingsRecord.setScore(standingsRecord.getScore() + problemScoreRecord.getScore()); + } else { + standingsRecord.setPenaltyPoints(standingsRecord.getPenaltyPoints() + problemScoreRecord.getPoints()); + } if (problemScoreRecord.getSolutionTime() > standingsRecord.getLastSolved()) { standingsRecord.setLastSolved(problemScoreRecord.getSolutionTime()); @@ -659,10 +702,12 @@ private StandingsRecord[] computeStandingStandingsRecords(Run[] runs, Account[] } problemNumber++; } - long penaltyPoints = standingsRecord.getPenaltyPoints(); - int scoreAdjustment = account.getScoringAdjustment(); - if (penaltyPoints > 0 && scoreAdjustment != 0) { - standingsRecord.setPenaltyPoints(Math.max(penaltyPoints+scoreAdjustment,0)); + if(!isPointScoring) { + long penaltyPoints = standingsRecord.getPenaltyPoints(); + int scoreAdjustment = account.getScoringAdjustment(); + if (penaltyPoints > 0 && scoreAdjustment != 0) { + standingsRecord.setPenaltyPoints(Math.max(penaltyPoints+scoreAdjustment,0)); + } } standingsRecords[standRecCount] = standingsRecord; standRecCount++; @@ -719,6 +764,8 @@ public ProblemScoreRecord createProblemScoreRecord(Run[] runs, Problem problem, int numberJudged = 0; + double score = 0; + Arrays.sort(runs, new RunCompartorByElapsed()); for (Run run : runs) { @@ -736,11 +783,27 @@ public ProblemScoreRecord createProblemScoreRecord(Run[] runs, Problem problem, numberPending++; } - if (run.isSolved() && solutionTime == 0) { - // set to solved, set solution time - solved = true; - solutionTime = run.getElapsedMins(); - solvingRun = run; + if (run.isSolved()) { + if(isPointScoring) { + // Point scoring works somewhat differently, in that there may be more than one + // accepted solution. We look for the one with the biggest score. + JudgementRecord jr = run.getJudgementRecord(); + if(jr != null) { + double dScore; + dScore = jr.getScore(); + if(dScore > score) { + score = dScore; + solutionTime = run.getElapsedMins(); + solvingRun = run; + solved = true; + } + } + } else if(solutionTime == 0) { + // set to solved, set solution time + solved = true; + solutionTime = run.getElapsedMins(); + solvingRun = run; + } } if (run.isJudged() && (!solved)) { @@ -767,8 +830,11 @@ public ProblemScoreRecord createProblemScoreRecord(Run[] runs, Problem problem, (securityViolationBeforeYes * getSVPenalty(properties)); } - return new ProblemScoreRecord(solved, solvingRun, problem, points, solutionTime, numberSubmissions, submissionsBeforeYes, numberPending, numberJudged); - + ProblemScoreRecord psr = new ProblemScoreRecord(solved, solvingRun, problem, points, solutionTime, numberSubmissions, submissionsBeforeYes, numberPending, numberJudged); + if(isPointScoring) { + psr.setScore(score); + } + return(psr); } private int getCEPenalty(Properties properties) { @@ -794,6 +860,7 @@ private IMemento addProblemSummaryRow(IMemento mementoRoot, int index, ProblemSu summaryInfoMemento.putString("shortName", summaryInfo.getShortName()); summaryInfoMemento.putInteger("attempts", summaryInfo.getNumberSubmitted()); summaryInfoMemento.putInteger("points", summaryInfo.getPenaltyPoints()); + summaryInfoMemento.putDouble("score", summaryInfo.getScore()); summaryInfoMemento.putLong("solutionTime", summaryInfo.getSolutionTime()); summaryInfoMemento.putBoolean("isSolved", summaryInfo.isSolved()); summaryInfoMemento.putBoolean("isPending", summaryInfo.isUnJudgedRuns()); @@ -806,7 +873,11 @@ private ProblemSummaryInfo createProblemSummaryInfo(Run[] runs, Problem problem, summaryInfo.setNumberSubmitted(problemScoreRecord.getNumberSubmissions()); summaryInfo.setJudgedRunCount(problemScoreRecord.getNumberJudgedSubmissions()); summaryInfo.setPendingRunCount(problemScoreRecord.getNumberPendingSubmissions()); - summaryInfo.setPenaltyPoints((int) problemScoreRecord.getPoints()); + if(isPointScoring) { + summaryInfo.setScore(problemScoreRecord.getScore()); + } else { + summaryInfo.setPenaltyPoints((int) problemScoreRecord.getPoints()); + } summaryInfo.setSolutionTime(problemScoreRecord.getSolutionTime()); summaryInfo.setUnJudgedRuns(false); summaryInfo.setSolved(problemScoreRecord.isSolved()); @@ -978,6 +1049,7 @@ private IMemento createSummaryMomento(ContestInformation contestInformation, XML memento.putString("systemVersion", versionInfo.getVersionNumber() + " build " + versionInfo.getBuildNumber()); memento.putString("systemURL", versionInfo.getSystemURL()); memento.putString("currentDate", new Date().toString()); + memento.putString("scoreType", contestInformation.getScoreboardType().toString().toLowerCase()); memento.putString("generatorId", "$Id$"); return memento; diff --git a/src/edu/csus/ecs/pc2/core/scoring/ProblemScoreRecord.java b/src/edu/csus/ecs/pc2/core/scoring/ProblemScoreRecord.java index c04af3c16..07b9dd715 100644 --- a/src/edu/csus/ecs/pc2/core/scoring/ProblemScoreRecord.java +++ b/src/edu/csus/ecs/pc2/core/scoring/ProblemScoreRecord.java @@ -8,7 +8,7 @@ /** * Holds summary score information for a single Problem. - * + * * @author pc2@ecs.csus.edu * @version $Id: ProblemScoreRecord.java 181 2011-04-11 03:21:46Z laned $ */ @@ -34,8 +34,10 @@ public class ProblemScoreRecord { private int submissionsBeforeYes; + private double score = 0; + /** - * + * * @param solved * @param solvingRun * @param problem @@ -62,7 +64,7 @@ public ProblemScoreRecord(boolean solved, Run solvingRun, Problem problem, long /** * Constructor deprecated. - * + * * This class is now used to store values, before it was used to both calculate and store values.
* See {@link edu.csus.ecs.pc2.core.scoring.NewScoringAlgorithm#createProblemScoreRecord(Run[], Problem, Properties)} as an example of how to compute values. */ @@ -73,7 +75,7 @@ public ProblemScoreRecord(Run[] teamProblemRuns, Problem problem2, Properties pr /** * Has problem been solved?. - * + * * @return true if problem solved. */ public boolean isSolved() { @@ -86,7 +88,7 @@ public long getSolutionTime() { /** * All non-deleted Runs. - * + * * @return */ public int getNumberSubmissions() { @@ -95,7 +97,7 @@ public int getNumberSubmissions() { /** * Time/Penalty points for this problem. - * + * * @return number of points */ public long getPoints() { @@ -104,7 +106,7 @@ public long getPoints() { /** * First run which solved this Problem. - * + * * @return null if run solved the problem. */ public Run getSolvingRun() { @@ -113,7 +115,7 @@ public Run getSolvingRun() { /** * Number of submissions before first yes. - * + * * @return */ public int getSubmissionsBeforeYes() { @@ -141,4 +143,12 @@ public int getNumberPendingSubmissions() { return numberPendingSubmissions; } + public double getScore() { + return score; + } + + public void setScore(double score) { + this.score = score; + } + } diff --git a/src/edu/csus/ecs/pc2/exports/ccs/ResultsFile.java b/src/edu/csus/ecs/pc2/exports/ccs/ResultsFile.java index 222dbe6d6..1afd52210 100644 --- a/src/edu/csus/ecs/pc2/exports/ccs/ResultsFile.java +++ b/src/edu/csus/ecs/pc2/exports/ccs/ResultsFile.java @@ -1,6 +1,7 @@ // Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. package edu.csus.ecs.pc2.exports.ccs; +import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -17,6 +18,7 @@ import edu.csus.ecs.pc2.core.model.IInternalContest; import edu.csus.ecs.pc2.core.scoring.CitationRankInformation; import edu.csus.ecs.pc2.core.scoring.DefaultScoringAlgorithm; +import edu.csus.ecs.pc2.core.scoring.FinalsStandingsPointScoringRecordComparator; import edu.csus.ecs.pc2.core.scoring.FinalsStandingsRecordComparator; import edu.csus.ecs.pc2.core.scoring.NewScoringAlgorithm; import edu.csus.ecs.pc2.core.scoring.StandingsRecord; @@ -56,8 +58,6 @@ public class ResultsFile { private FinalizeData finalizeData = null; - private FinalsStandingsRecordComparator comparator; - public void setFinalizeData(FinalizeData finalizeData) { this.finalizeData = finalizeData; } @@ -134,6 +134,9 @@ private int getMedian(StandingsRecord[] srArray) { public String[] createFileLines(IInternalContest contest, Group group, String resultFileTitleFieldName, boolean isTSV) { Vector lines = new Vector(); + boolean isPointScoring = contest.getContestInformation().isScoreboardTypeScore(); + + DecimalFormat dblFormatter = null; finalizeData = contest.getFinalizeData(); @@ -149,7 +152,11 @@ public String[] createFileLines(IInternalContest contest, Group group, String re if (isTSV) { lines.addElement(resultFileTitleFieldName + TAB + "1"); } else { - lines.addElement("teamId,rank,medalCitation,problemsSolved,totalTime,lastProblemTime,siteCitation,citation"); + if(isPointScoring) { + lines.addElement("teamId,rank,medalCitation,problemsSolved,score,time,siteCitation,citation"); + } else { + lines.addElement("teamId,rank,medalCitation,problemsSolved,totalTime,lastProblemTime,siteCitation,citation"); + } } // return ranked teams @@ -197,12 +204,19 @@ public String[] createFileLines(IInternalContest contest, Group group, String re for (Account account : accounts) { accountList.add(account); } - comparator = new FinalsStandingsRecordComparator(); - comparator.setCachedAccountList(accountList); - comparator.setLastRank(lastMedalRank); - comparator.setMedian(median); - comparator.setUseWFGroupRanking(finalizeData.isUseWFGroupRanking()); - Arrays.sort(standingsRecords, comparator); + if(isPointScoring) { + FinalsStandingsPointScoringRecordComparator comparator = new FinalsStandingsPointScoringRecordComparator(); + comparator.setCachedAccountList(accountList); + Arrays.sort(standingsRecords, comparator); + dblFormatter = new DecimalFormat("0.00##"); + } else { + FinalsStandingsRecordComparator comparator = new FinalsStandingsRecordComparator(); + comparator.setCachedAccountList(accountList); + comparator.setLastRank(lastMedalRank); + comparator.setMedian(median); + comparator.setUseWFGroupRanking(finalizeData.isUseWFGroupRanking()); + Arrays.sort(standingsRecords, comparator); + } int realRank = 0; if (highestHonorSolvedCount == 0) { @@ -231,15 +245,21 @@ public String[] createFileLines(IInternalContest contest, Group group, String re boolean isHighHonor = false; boolean isHonor = false; - if (finalizeData.isUseWFGroupRanking()) { - if (record.getNumberSolved() >= highestHonorSolvedCount) { - isHighestHonor = true; - } else if (record.getNumberSolved() >= highHonorSolvedCount) { - isHighHonor = true; + // Sorry, no "Bill" rules for point scoring contests as it doesnt make sense the way its defined. + if(!isPointScoring) { + if (finalizeData.isUseWFGroupRanking()) { + if (record.getNumberSolved() >= highestHonorSolvedCount) { + isHighestHonor = true; + } else if (record.getNumberSolved() >= highHonorSolvedCount) { + isHighHonor = true; + } else if (record.getNumberSolved() >= median) { + isHonor = true; + } } else if (record.getNumberSolved() >= median) { isHonor = true; } - } else if (record.getNumberSolved() >= median) { + } else { + // This will force "RANKED" in a kludgy way isHonor = true; } @@ -250,7 +270,7 @@ public String[] createFileLines(IInternalContest contest, Group group, String re String rank = ""; if (!HONORABLE.equalsIgnoreCase(award)) { - if (finalizeData.isUseWFGroupRanking() && realRank > lastMedalRank) { + if (!isPointScoring && finalizeData.isUseWFGroupRanking() && realRank > lastMedalRank) { if (record.getNumberSolved() != lastSolvedNum) { lastSolvedNum = record.getNumberSolved(); rankNumber = realRank; @@ -260,23 +280,33 @@ public String[] createFileLines(IInternalContest contest, Group group, String re rank = Integer.toString(record.getRankNumber()); } + String awardLine; if (isTSV) { - lines.addElement(reservationId + TAB // + awardLine = reservationId + TAB // + rank + TAB // + award + TAB // - + record.getNumberSolved() + TAB // - + record.getPenaltyPoints() + TAB // - + record.getLastSolved()); + + record.getNumberSolved() + TAB; + if(isPointScoring) { + awardLine += dblFormatter.format(record.getScore()); + } else { + awardLine += record.getPenaltyPoints(); + } + awardLine += TAB + record.getLastSolved(); } else { - // teamId,rank,medalCitation,problemsSolved,totalTime,lastProblemTime,siteCitation,citation - lines.addElement(reservationId + COMMA // + // teamId,rank,medalCitation,problemsSolved,totalTime|score,lastProblemTime,siteCitation,citation + awardLine = reservationId + COMMA // + rank + COMMA // + award + COMMA // - + record.getNumberSolved() + COMMA // - + record.getPenaltyPoints() + COMMA // - + record.getLastSolved() + COMMA // then siteCitation - + COMMA); // then citation + + record.getNumberSolved() + COMMA; + if(isPointScoring) { + awardLine += dblFormatter.format(record.getScore()); + } else { + awardLine += record.getPenaltyPoints(); + } + awardLine += COMMA + record.getLastSolved() + COMMA // then siteCitation + + COMMA; // then citation } + lines.addElement(awardLine); } return lines.toArray(new String[lines.size()]); @@ -297,6 +327,8 @@ public CitationRankInformation createCitationRankInformation(IInternalContest co int highestHonorSolvedCount = 0; int highHonorSolvedCount = 0; CitationRankInformation ri = new CitationRankInformation(); + boolean isPointScoring = contest.getContestInformation().isScoreboardTypeScore(); + DecimalFormat dblFormatter; finalizeData = contest.getFinalizeData(); if (finalizeData == null) { @@ -320,7 +352,7 @@ public CitationRankInformation createCitationRankInformation(IInternalContest co return(ri); } - if (finalizeData.isUseWFGroupRanking() && finalizeData.isCustomizeHonorsSolvedCount()) { + if (!isPointScoring && finalizeData.isUseWFGroupRanking() && finalizeData.isCustomizeHonorsSolvedCount()) { if (finalizeData.getHighestHonorSolvedCount() != 0) { highestHonorSolvedCount = finalizeData.getHighestHonorSolvedCount(); } @@ -339,12 +371,19 @@ public CitationRankInformation createCitationRankInformation(IInternalContest co for (Account account : accounts) { accountList.add(account); } - comparator = new FinalsStandingsRecordComparator(); - comparator.setCachedAccountList(accountList); - comparator.setLastRank(lastMedalRank); - comparator.setMedian(median); - comparator.setUseWFGroupRanking(finalizeData.isUseWFGroupRanking()); - Arrays.sort(standingsRecords, comparator); + if(isPointScoring) { + FinalsStandingsPointScoringRecordComparator comparator = new FinalsStandingsPointScoringRecordComparator(); + comparator.setCachedAccountList(accountList); + Arrays.sort(standingsRecords, comparator); + dblFormatter = new DecimalFormat("0.00##"); + } else { + FinalsStandingsRecordComparator comparator = new FinalsStandingsRecordComparator(); + comparator.setCachedAccountList(accountList); + comparator.setLastRank(lastMedalRank); + comparator.setMedian(median); + comparator.setUseWFGroupRanking(finalizeData.isUseWFGroupRanking()); + Arrays.sort(standingsRecords, comparator); + } int rank; if (highestHonorSolvedCount == 0) { @@ -360,7 +399,7 @@ public CitationRankInformation createCitationRankInformation(IInternalContest co boolean isHighHonor = false; boolean isHonor = false; - if (finalizeData.isUseWFGroupRanking()) { + if (!isPointScoring && finalizeData.isUseWFGroupRanking()) { if (record.getNumberSolved() >= highestHonorSolvedCount) { isHighestHonor = true; } else if (record.getNumberSolved() >= highHonorSolvedCount) { @@ -377,7 +416,7 @@ public CitationRankInformation createCitationRankInformation(IInternalContest co rank = record.getRankNumber(); if (record.getNumberSolved() > 0 && !HONORABLE.equalsIgnoreCase(getMedalCitation(rank, finalizeData, isHighestHonor, isHighHonor, isHonor))) { - if (finalizeData.isUseWFGroupRanking()) { + if (!isPointScoring && finalizeData.isUseWFGroupRanking()) { if(isHighestHonor) { ri.updateLastHighestHonorsRank(rank); } else if(isHighHonor) { diff --git a/src/edu/csus/ecs/pc2/graders/LegacyGrader.java b/src/edu/csus/ecs/pc2/graders/LegacyGrader.java index 9ddb9d5ee..60a1942fd 100644 --- a/src/edu/csus/ecs/pc2/graders/LegacyGrader.java +++ b/src/edu/csus/ecs/pc2/graders/LegacyGrader.java @@ -1,6 +1,7 @@ // Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. package edu.csus.ecs.pc2.graders; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Scanner; @@ -52,6 +53,22 @@ enum JudgmentCodes { private boolean ignoreSample = false; private int graderError = 0; + private String logFile = null; + private PrintWriter debugStream = null; + + public LegacyGrader() { + + } + + public LegacyGrader(String logFile) { + this.logFile = logFile; + try { + debugStream = new PrintWriter(logFile, "UTF-8"); + } catch(Exception e) { + System.err.println("LegacyGrader: Can not crete debug log file " + logFile + ": " + e); + } + } + /** * See if the supplied string argument is a valid verdict mode. * @@ -170,9 +187,22 @@ public String gradeTestCases(ArrayList testCaseResults) { String firstError = null; String graderResult = null; + if(debugStream != null) { + debugStream.println("Grader Settings:"); + debugStream.println(" verdictMode = " + verdictMode); + debugStream.println(" scoringMode = " + scoringMode); + debugStream.println(" acceptIfAnyAccepted = " + acceptIfAnyAccepted); + debugStream.println(" ignoreSample = " + ignoreSample); + debugStream.println("TestCases:"); + } + graderError = 0; for(String line : testCaseResults) { nLine++; + if(debugStream != null) { + debugStream.printf("%3d:%s", nLine, line); + debugStream.println(); + } /* * A little explanation here about ignoring samples. * The Legacy Grader specification says: @@ -193,6 +223,9 @@ public String gradeTestCases(ArrayList testCaseResults) { break; } ignoreSampleGroup = false; + if(debugStream != null) { + debugStream.println("Ignored previous line due to ignore_sample"); + } continue; } String [] values = line.trim().split("\\s+"); @@ -278,7 +311,7 @@ public String gradeTestCases(ArrayList testCaseResults) { } } else { // determine non-accepted judgment - // All cases should either print the correct output to stdout, or print + // All cases should either save the correct output to graderResult, or print // an error to stderr and set graderError to a non-zero value. switch(verdictMode) { case worst_error: @@ -286,7 +319,7 @@ public String gradeTestCases(ArrayList testCaseResults) { for(JudgmentCodes jcode : JudgmentCodes.values()) { idx = jcode.ordinal(); if(idx > 0 && sawJudgment[idx]) { - System.out.println(jcode.toString() + " 0"); + graderResult = jcode.toString() + " 0"; found = true; break; } @@ -304,7 +337,7 @@ public String gradeTestCases(ArrayList testCaseResults) { System.err.println("LegacyGrader: FATAL error - can not find judgment code for first_error mode."); graderError = GRADER_ERROR_BAD_FIRST_CODE; } else { - System.out.println(firstError + " 0"); + graderResult = firstError + " 0"; } break; @@ -316,6 +349,15 @@ public String gradeTestCases(ArrayList testCaseResults) { } } } + if(debugStream != null) { + debugStream.println(); + if(graderResult != null) { + debugStream.println("RESULT:" + graderResult); + } else { + debugStream.println("ERROR: Grader error " + graderError); + } + debugStream.flush(); + } // this will be null in the case of an error, in which case graderError will have the error code // in the case of success, this will be the "judgment_acronym score", eg. "AC 50" return graderResult; diff --git a/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java b/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java index f61127871..89b95b101 100644 --- a/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java +++ b/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java @@ -422,6 +422,10 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S String teamScoreboadDisplayString = ContestImportUtilities.fetchValue(content, TEAM_SCOREBOARD_DISPLAY_FORMAT_STRING, contestInformation.getTeamScoreboardDisplayFormat()); contestInformation.setTeamScoreboardDisplayFormat(teamScoreboadDisplayString); + // control submission throttling + boolean submissionThrottling = ContestImportUtilities.fetchBooleanValue(content, SUBMISSION_THROTTLING_KEY, contestInformation.isSubmissionThrottling()); + contestInformation.setSubmissionThrottling(submissionThrottling); + // enable shadow mode boolean shadowMode = ContestImportUtilities.fetchBooleanValue(content, SHADOW_MODE_KEY, contestInformation.isShadowMode()); contestInformation.setShadowMode(shadowMode); @@ -624,15 +628,17 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S // If the contest type is present in contest.yaml, verify it String scoreType = ContestImportUtilities.fetchValue(content, CLICS_CONTEST_SCOREBOARD_TYPE); - if(scoreType != null && !scoreType.equals(CLICS_CONTEST_SCOREBOARD_TYPE_PASSFAIL) + if(scoreType != null) { + if(!scoreType.equals(CLICS_CONTEST_SCOREBOARD_TYPE_PASSFAIL) && !scoreType.equals(CLICS_CONTEST_SCOREBOARD_TYPE_SCORE)) { - throw new YamlLoadException("Invalid " + CLICS_CONTEST_SCOREBOARD_TYPE + ": " - + scoreType + ", expected " - + CLICS_CONTEST_SCOREBOARD_TYPE_PASSFAIL - + " or " - + CLICS_CONTEST_SCOREBOARD_TYPE_SCORE); + throw new YamlLoadException("Invalid " + CLICS_CONTEST_SCOREBOARD_TYPE + ": " + + scoreType + ", expected " + + CLICS_CONTEST_SCOREBOARD_TYPE_PASSFAIL + + " or " + + CLICS_CONTEST_SCOREBOARD_TYPE_SCORE); + } + setContestScoreboardType(contest, scoreType); } - setContestScoreboardType(contest, scoreType); Object privatehtmlOutputDirectory = ContestImportUtilities.fetchObjectValue(content, OUTPUT_PRIVATE_SCORE_DIR_KEY); if (privatehtmlOutputDirectory != null) { diff --git a/src/edu/csus/ecs/pc2/imports/ccs/IContestLoader.java b/src/edu/csus/ecs/pc2/imports/ccs/IContestLoader.java index ffce2d32c..8e3dfd319 100644 --- a/src/edu/csus/ecs/pc2/imports/ccs/IContestLoader.java +++ b/src/edu/csus/ecs/pc2/imports/ccs/IContestLoader.java @@ -251,6 +251,8 @@ public interface IContestLoader { */ String TEAM_SCOREBOARD_DISPLAY_FORMAT_STRING = "team-scoreboard-display-format-string"; + String SUBMISSION_THROTTLING_KEY = "submission-throttling"; + Problem addDefaultPC2Validator(Problem problem, int optionNumber); void dumpSerialzedFileList(Problem problem, String logPrefixId, SerializedFile[] sfList); diff --git a/src/edu/csus/ecs/pc2/ui/DoubleDocument.java b/src/edu/csus/ecs/pc2/ui/DoubleDocument.java new file mode 100644 index 000000000..2c5d5bae3 --- /dev/null +++ b/src/edu/csus/ecs/pc2/ui/DoubleDocument.java @@ -0,0 +1,48 @@ +// Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. +package edu.csus.ecs.pc2.ui; + +import java.awt.Toolkit; + +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.PlainDocument; + +/** + * Accept Double input only. + * + * @see javax.swing.JTextField#setDocument(Document) + * @author John Buck + */ +// $HeadURL$ +public class DoubleDocument extends PlainDocument { + + /** + * + */ + private static final long serialVersionUID = 1L; + + @Override + public void insertString(int offset, String string, AttributeSet attributes) throws BadLocationException { + + if (string != null) { + String newValue; + int length = getLength(); + if (length == 0) { + newValue = string; + } else { + String currentContent = getText(0, length); + StringBuffer currentBuffer = new StringBuffer(currentContent); + currentBuffer.insert(offset, string); + newValue = currentBuffer.toString(); + } + try { + if (!newValue.equals("")) { + Double.parseDouble(newValue); + } + super.insertString(offset, string, attributes); + } catch (NumberFormatException exception) { + Toolkit.getDefaultToolkit().beep(); + } + } + } +} diff --git a/src/edu/csus/ecs/pc2/ui/EditRunPane.java b/src/edu/csus/ecs/pc2/ui/EditRunPane.java index 3aed95ae9..b0cd6f337 100644 --- a/src/edu/csus/ecs/pc2/ui/EditRunPane.java +++ b/src/edu/csus/ecs/pc2/ui/EditRunPane.java @@ -1,8 +1,10 @@ -// Copyright (C) 1989-2023 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. +// Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. package edu.csus.ecs.pc2.ui; import java.awt.BorderLayout; +import java.awt.Dimension; import java.awt.FlowLayout; +import java.awt.Point; import java.awt.event.KeyAdapter; import java.io.File; import java.io.IOException; @@ -41,12 +43,10 @@ import edu.csus.ecs.pc2.core.model.SerializedFile; import edu.csus.ecs.pc2.core.report.ExtractRuns; import edu.csus.ecs.pc2.core.security.FileSecurityException; -import java.awt.Point; -import java.awt.Dimension; /** * Add/Edit Run Pane - * + * * @author pc2@ecs.csus.edu * @version $Id$ */ @@ -55,7 +55,7 @@ public class EditRunPane extends JPanePlugin { /** - * + * */ private static final long serialVersionUID = 8747938709622932819L; @@ -98,7 +98,7 @@ public class EditRunPane extends JPanePlugin { private JLabel statusTitleLabel = null; private JComboBox problemComboBox = null; - + private JComboBox runStatusComboBox = null; private JComboBox languageComboBox = null; @@ -109,8 +109,12 @@ public class EditRunPane extends JPanePlugin { private JLabel jLabel = null; + private JLabel scoreLabel = null; + private JTextField elapsedTimeTextField = null; + private JTextField scoreTextField = null; + private IFileViewer sourceViewer; private JCheckBox notifyTeamCheckBox = null; @@ -121,7 +125,7 @@ public class EditRunPane extends JPanePlugin { /** * This method initializes - * + * */ public EditRunPane() { super(); @@ -130,7 +134,7 @@ public EditRunPane() { /** * This method initializes this - * + * */ private void initialize() { this.setLayout(new BorderLayout()); @@ -142,35 +146,39 @@ private void initialize() { this.add(getGeneralPane(), java.awt.BorderLayout.CENTER); } + @Override public void setContestAndController(IInternalContest inContest, IInternalController inController) { super.setContestAndController(inContest, inController); log = getController().getLog(); addWindowCloserListener(); extractRuns = new ExtractRuns(inContest); } - + private void addWindowCloserListener() { SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { if (getParentFrame() != null) { getParentFrame().addWindowListener(new java.awt.event.WindowAdapter() { + @Override public void windowClosing(java.awt.event.WindowEvent e) { handleCancelButton(); } }); - } + } } }); } + @Override public String getPluginTitle() { return "Edit Run Pane"; } /** * This method initializes messagePane - * + * * @return javax.swing.JPanel */ private JPanel getMessagePane() { @@ -189,7 +197,7 @@ private JPanel getMessagePane() { /** * This method initializes buttonPane - * + * * @return javax.swing.JPanel */ private JPanel getButtonPane() { @@ -214,7 +222,7 @@ private Run getRunFromFields() { /** * This method initializes updateButton - * + * * @return javax.swing.JButton */ private JButton getUpdateButton() { @@ -224,6 +232,7 @@ private JButton getUpdateButton() { updateButton.setEnabled(false); updateButton.setMnemonic(java.awt.event.KeyEvent.VK_U); updateButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { updateRun(); } @@ -249,11 +258,13 @@ protected void updateRun() { Judgement judgement = (Judgement) getJudgementComboBox().getSelectedItem(); judgementRecord = new JudgementRecord(judgement.getElementId(), getContest().getClientId(), solved, false); + double score = getDoubleValue(getScoreTextField().getText()); + judgementRecord.setScore(score); judgementRecord.setSendToTeam(getNotifyTeamCheckBox().isSelected()); } newRun.setDeleted(deleteCheckBox.isSelected()); - + int elapsed = getIntegerValue(getElapsedTimeTextField().getText()); newRun.setElapsedMins(elapsed); @@ -271,7 +282,7 @@ protected void updateRun() { RunStates prevState = run.getStatus(); RunStates newRunState = (Run.RunStates) runStatusComboBox.getSelectedItem(); String errMsg = null; - + // Make sure it's safe to change the runstate switch(newRunState) { case JUDGED: @@ -283,7 +294,7 @@ protected void updateRun() { errMsg = "The run does not have any judgment records"; } break; - + case BEING_RE_JUDGED: // It is completely unreasonable to set a run's status to being re-judged errMsg = "You are not allowed to set the Run Status to BEING_RE_JUDGED"; @@ -297,7 +308,7 @@ protected void updateRun() { if(errMsg != null) { FrameUtilities.showMessage(this, "Can not set Run Status", errMsg); enableUpdateButton(); - return; + return; } int result = FrameUtilities.yesNoCancelDialog(this, "Are you sure you want to change status from " + // prevState.toString() + " to " + newRunState.toString() + "?", "Update/Change run status?"); @@ -314,19 +325,19 @@ protected void updateRun() { if (executable != null) { executionData = executable.getExecutionData(); } - + runResultFiles = new RunResultFiles(newRun, newRun.getProblemId(), judgementRecord, executionData); getController().updateRun(newRun, judgementRecord, runResultFiles); if (getParentFrame() != null) { getParentFrame().setVisible(false); } - + } /** * This method initializes cancelButton - * + * * @return javax.swing.JButton */ private JButton getCancelButton() { @@ -335,6 +346,7 @@ private JButton getCancelButton() { cancelButton.setText("Cancel"); cancelButton.setMnemonic(java.awt.event.KeyEvent.VK_C); cancelButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { handleCancelButton(); } @@ -382,6 +394,7 @@ public void setRun(final Run run) { FrameUtilities.waitCursor(this); SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { populateGUI(run); enableUpdateButtons(false); @@ -390,7 +403,7 @@ public void run() { } private void populateGUI(Run run2) { - + populatingGUI = true; if (run2 != null) { @@ -402,7 +415,13 @@ private void populateGUI(Run run2) { runInfoLabel.setText("Run " + run2.getNumber() + " (Site " + run2.getSiteNumber() + ") from " + teamName); deleteCheckBox.setSelected(run2.isDeleted()); elapsedTimeTextField.setText(new Long(run.getElapsedMins()).toString()); - + JudgementRecord judgementRecord = run2.getJudgementRecord(); + double score = 0; + if(judgementRecord != null) { + score = judgementRecord.getScore(); + } + getScoreTextField().setText(new Double(score).toString()); + getNotifyTeamCheckBox().setSelected(notifyTeam()); } else { @@ -411,11 +430,12 @@ private void populateGUI(Run run2) { runInfoLabel.setText("Could not get run"); deleteCheckBox.setSelected(false); elapsedTimeTextField.setText(""); + getScoreTextField().setText("0"); getNotifyTeamCheckBox().setSelected(false); } populateComboBoxes(); - + populatingGUI = false; } @@ -428,7 +448,7 @@ private void populateComboBoxes() { getProblemComboBox().removeAllItems(); getLanguageComboBox().removeAllItems(); getJudgementComboBox().removeAllItems(); - + runStatusComboBox.removeAllItems(); if (run == null) { @@ -482,7 +502,7 @@ private void populateComboBoxes() { log.info("Edit Run " + run.getNumber() + " judgement is " + judgementDescription); judgementLabel.setToolTipText(judgementDescription); } - + for (Judgement judgement : getContest().getJudgements()) { getJudgementComboBox().addItem(judgement); if (judgement.getElementId().equals(judgementId)) { @@ -490,15 +510,15 @@ private void populateComboBoxes() { } index++; } - + // Select judgement in combo box judgementComboBox.setSelectedIndex(selectedIndex); - + selectedIndex = -1; index = 0; runStatusComboBox.setSelectedIndex(selectedIndex); - + RunStates[] states = Run.RunStates.values(); for (RunStates runStates : states) { runStatusComboBox.addItem(runStates); @@ -507,7 +527,7 @@ private void populateComboBoxes() { } index++; } - + runStatusComboBox.setSelectedIndex(selectedIndex); } @@ -535,9 +555,17 @@ private int getIntegerValue(String s) { } } + private double getDoubleValue(String s) { + try { + return Double.parseDouble(s); + } catch (Exception e) { + return 0.0; + } + } + /** * Enable or disable Update button based on comparison of run to fields. - * + * */ public void enableUpdateButton() { @@ -560,11 +588,11 @@ public void enableUpdateButton() { enableButton |= (run.isDeleted() != getDeleteCheckBox().isSelected()); enableButton |= judgementChanged(); - + enableButton |= isStatusChanged(); - + enableButton |= notifyTeamChanged(); - + } getUpdateButton().setEnabled(enableButton); @@ -573,7 +601,7 @@ public void enableUpdateButton() { /** * return if status changed by user. - * + * * @return true if input run status different than combobox status */ private boolean isStatusChanged() { @@ -586,10 +614,13 @@ private boolean isStatusChanged() { private boolean judgementChanged() { if (run.isJudged()) { - + if (notifyTeamChanged()){ return true; } + if (scoreChanged()) { + return true; + } Judgement judgement = (Judgement) getJudgementComboBox().getSelectedItem(); if (judgement != null) { @@ -603,9 +634,20 @@ private boolean judgementChanged() { return false; } + private boolean scoreChanged() { + if (run.isJudged()) { + JudgementRecord judgementRecord = run.getJudgementRecord(); + if(judgementRecord != null) { + return(judgementRecord.getScore() != getDoubleValue(getScoreTextField().getText())); + } + } + + return false; + } + /** * For this run, send notification to team? - * @return + * @return */ private boolean notifyTeam(){ if (run.isJudged()) { @@ -618,7 +660,7 @@ private boolean notifyTeam(){ } return false; } - + /** * Has the notify team status changed ? * @return true if changed, false otherwise. @@ -629,7 +671,7 @@ private boolean notifyTeamChanged() { /** * This method initializes mainTabbedPane - * + * * @return javax.swing.JTabbedPane */ private JTabbedPane getMainTabbedPane() { @@ -641,7 +683,7 @@ private JTabbedPane getMainTabbedPane() { /** * This method initializes generalPane - * + * * @return javax.swing.JPanel */ private JPanel getGeneralPane() { @@ -650,6 +692,10 @@ private JPanel getGeneralPane() { jLabel.setBounds(new java.awt.Rectangle(73, 67, 142, 16)); jLabel.setText("Elapsed"); jLabel.setHorizontalAlignment(SwingConstants.RIGHT); + scoreLabel = new JLabel(); + scoreLabel.setBounds(new java.awt.Rectangle(300, 67, 65, 16)); + scoreLabel.setText("Score"); + scoreLabel.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); languageLabel = new JLabel(); languageLabel.setBounds(new java.awt.Rectangle(73, 166, 142, 16)); languageLabel.setText("Language"); @@ -684,12 +730,16 @@ private JPanel getGeneralPane() { generalPane.add(languageLabel, null); generalPane.add(jLabel, null); generalPane.add(getElapsedTimeTextField(), null); + generalPane.add(scoreLabel, null); + generalPane.add(getScoreTextField(), null); generalPane.add(getNotifyTeamCheckBox(), null); - + runStatusComboBox = new JComboBox(); runStatusComboBox.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { new Thread(new Runnable() { + @Override public void run() { enableUpdateButton(); } @@ -707,6 +757,7 @@ public void run() { public void showMessage(final String message) { SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { messageLabel.setText(message); } @@ -715,7 +766,7 @@ public void run() { /** * This method initializes executeButton - * + * * @return javax.swing.JButton */ private JButton getExecuteButton() { @@ -724,8 +775,10 @@ private JButton getExecuteButton() { executeButton.setText("Execute"); executeButton.setMnemonic(java.awt.event.KeyEvent.VK_X); executeButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { new Thread(new Runnable() { + @Override public void run() { executeRun(); } @@ -738,7 +791,7 @@ public void run() { /** * This method initializes viewSourceButton - * + * * @return javax.swing.JButton */ private JButton getViewSourceButton() { @@ -747,6 +800,7 @@ private JButton getViewSourceButton() { viewSourceButton.setText("View Source"); viewSourceButton.setMnemonic(java.awt.event.KeyEvent.VK_V); viewSourceButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { viewSourceFile(); } @@ -763,7 +817,7 @@ protected void viewSourceFile() { /** * This method initializes extractButton - * + * * @return javax.swing.JButton */ private JButton getExtractButton() { @@ -773,6 +827,7 @@ private JButton getExtractButton() { extractButton.setToolTipText("Extract Run contents"); extractButton.setMnemonic(java.awt.event.KeyEvent.VK_T); extractButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { extractRun(); } @@ -804,20 +859,20 @@ protected void extractRun() { protected void executeRun() { System.gc(); - + ExecuteTimerFrame executeFrame = new ExecuteTimerFrame(); - + executable = new Executable(getContest(), getController(), run, runFiles, executeFrame); IFileViewer fileViewer = executable.execute(); - + // Dump execution results files to log String executeDirctoryName = JudgementUtilities.getExecuteDirectoryName(getContest().getClientId()); Problem problem = getContest().getProblem(run.getProblemId()); ClientId clientId = getContest().getClientId(); List judgements = JudgementUtilities.getLastTestCaseJudgementList(getContest(), run); JudgementUtilities.dumpJudgementResultsToLog(log, clientId, run, executeDirctoryName, problem, judgements, executable.getExecutionData(), "", new Properties()); - + fileViewer.setVisible(true); } @@ -831,6 +886,7 @@ public void setRunAndFiles(Run run2, RunFiles runFiles2) { run = run2; runFiles = runFiles2; SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { populateGUI(run); enableUpdateButtons(false); @@ -841,7 +897,7 @@ public void run() { /** * This method initializes judgementComboBox - * + * * @return javax.swing.JComboBox */ private JComboBox getJudgementComboBox() { @@ -850,6 +906,7 @@ private JComboBox getJudgementComboBox() { judgementComboBox.setLocation(new java.awt.Point(224, 97)); judgementComboBox.setSize(new java.awt.Dimension(263, 22)); judgementComboBox.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { enableUpdateButton(); } @@ -860,7 +917,7 @@ public void actionPerformed(java.awt.event.ActionEvent e) { /** * This method initializes deleteCheckBox - * + * * @return javax.swing.JCheckBox */ private JCheckBox getDeleteCheckBox() { @@ -869,6 +926,7 @@ private JCheckBox getDeleteCheckBox() { deleteCheckBox.setBounds(new java.awt.Rectangle(224, 196, 114, 21)); deleteCheckBox.setText("Delete Run"); deleteCheckBox.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { enableUpdateButton(); } @@ -879,7 +937,7 @@ public void actionPerformed(java.awt.event.ActionEvent e) { /** * This method initializes problemComboBox - * + * * @return javax.swing.JComboBox */ private JComboBox getProblemComboBox() { @@ -887,6 +945,7 @@ private JComboBox getProblemComboBox() { problemComboBox = new JComboBox(); problemComboBox.setBounds(new java.awt.Rectangle(224, 130, 263, 22)); problemComboBox.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { enableUpdateButton(); } @@ -897,7 +956,7 @@ public void actionPerformed(java.awt.event.ActionEvent e) { /** * This method initializes languageComboBox - * + * * @return javax.swing.JComboBox */ private JComboBox getLanguageComboBox() { @@ -905,6 +964,7 @@ private JComboBox getLanguageComboBox() { languageComboBox = new JComboBox(); languageComboBox.setBounds(new java.awt.Rectangle(224, 163, 263, 22)); languageComboBox.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { enableUpdateButton(); } @@ -915,7 +975,7 @@ public void actionPerformed(java.awt.event.ActionEvent e) { /** * This method initializes elapsedTimeTextField - * + * * @return javax.swing.JTextField */ private JTextField getElapsedTimeTextField() { @@ -926,6 +986,7 @@ private JTextField getElapsedTimeTextField() { elapsedTimeTextField.addKeyListener(new KeyAdapter() { // public void keyPressed(java.awt.event.KeyEvent e) { + @Override public void keyReleased(java.awt.event.KeyEvent e) { enableUpdateButton(); } @@ -934,6 +995,28 @@ public void keyReleased(java.awt.event.KeyEvent e) { return elapsedTimeTextField; } + /** + * This method initializes scoreTextField + * + * @return javax.swing.JTextField + */ + private JTextField getScoreTextField() { + if (scoreTextField == null) { + scoreTextField = new JTextField(); + scoreTextField.setBounds(new java.awt.Rectangle(380, 65, 65, 21)); + scoreTextField.setDocument(new DoubleDocument()); + + scoreTextField.addKeyListener(new KeyAdapter() { + // public void keyPressed(java.awt.event.KeyEvent e) { + @Override + public void keyReleased(java.awt.event.KeyEvent e) { + enableUpdateButton(); + } + }); + } + return scoreTextField; + } + private void createAndViewFile(SerializedFile file, String title) { // TODO the executeable dir name should be from the model, eh ? Executable tempEexecutable = new Executable(getContest(), getController(), run, runFiles, null); @@ -963,7 +1046,7 @@ private void createAndViewFile(SerializedFile file, String title) { /** * This method initializes notifyTeamCheckBox - * + * * @return javax.swing.JCheckBox */ private JCheckBox getNotifyTeamCheckBox() { @@ -973,6 +1056,7 @@ private JCheckBox getNotifyTeamCheckBox() { notifyTeamCheckBox.setSelected(false); notifyTeamCheckBox.setText("Notify Team"); notifyTeamCheckBox.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { enableUpdateButton(); }