From 6d849ec32da74a2e3467f638b0e984be822b95be Mon Sep 17 00:00:00 2001 From: John Buck Date: Tue, 6 Aug 2024 19:34:01 -0400 Subject: [PATCH 1/2] i_999 Add execute grace times for sandbox and interactive problems Add 2 new values to ContestInformation: sandboxGraceTimeSecs and sandboxInteractiveGraceMultiplier. These integer values may be configured using the system.pc2.yaml at contest load time, or the GUI Settings tab after a contest is configured. Change Executable to pad execution timers to wait additional time (grace time) for sandbox and interactive sandbox problems, where the sandbox will monitor the actual CPU time of the submission. The timers in PC2, in this case, are for Wall time to protect against sleeping/wall time consuming processes. --- .../csus/ecs/pc2/core/execute/Executable.java | 52 +- .../pc2/core/model/ContestInformation.java | 254 ++++---- .../imports/ccs/ContestSnakeYAMLLoader.java | 34 ++ .../ecs/pc2/imports/ccs/IContestLoader.java | 4 + .../ecs/pc2/ui/ContestInformationPane.java | 560 ++++++++++++------ 5 files changed, 596 insertions(+), 308 deletions(-) diff --git a/src/edu/csus/ecs/pc2/core/execute/Executable.java b/src/edu/csus/ecs/pc2/core/execute/Executable.java index 9d3ef1827..df8020016 100644 --- a/src/edu/csus/ecs/pc2/core/execute/Executable.java +++ b/src/edu/csus/ecs/pc2/core/execute/Executable.java @@ -171,11 +171,6 @@ public class Executable extends Plugin implements IExecutable, IExecutableNotify private static final long NANOSECS_PER_MILLISEC = 1_000_000; - /** - * Sandbox constants - */ - public static final long SANDBOX_EXTRA_KILLTIME_MS = 1000; - /** * Return codes from sandbox */ @@ -1815,8 +1810,29 @@ protected boolean executeProgram(int dataSetNumber) { log.log(Log.INFO, "Executing run " + run.getNumber() + " from " + run.getSubmitter().getTripletKey() + " test set " + testSetNumber); } - log.info("Constructing ExecuteTimer..."); - executionTimer = new ExecuteTimer(log, problem.getTimeOutInSeconds(), executorId, isUsingGUI() ? executionMonitor : null); + + //determine whether the Problem is configured to use a sandbox + usingSandbox = isUsingSandbox(); + + //set the TLE kill task delay and ExecuteTimer delay to the number of seconds allowed by the problem + int delaySecs = problem.getTimeOutInSeconds(); + + if(usingSandbox) { + ContestInformation contestInfo = contest.getContestInformation(); + + //add ample extra time for interactive problems due to overhead the Primary may have in logging; also + //interactive validators may spend time too. + if(problem.isInteractive()) { + delaySecs = delaySecs * contestInfo.getSandboxInteractiveGraceMultiplier(); + log.info ("multiplying time limit by " + contestInfo.getSandboxInteractiveGraceMultiplier() + " for interactive problem: " + + problem.getTimeOutInSeconds() + "secs -> " + delaySecs); + } else { + log.info ("adding " + contestInfo.getSandboxGraceTimeSecs() + " sec delay to TLE-Timer for sandbox"); + delaySecs += contestInfo.getSandboxGraceTimeSecs(); + } + } + log.info("Constructing ExecuteTimer of " + delaySecs + " seconds..."); + executionTimer = new ExecuteTimer(log, delaySecs, executorId, isUsingGUI() ? executionMonitor : null); log.info("Created new ExecuteTimer: " + executionTimer.toString()); if (problem.getDataFileName() != null) { @@ -1947,9 +1963,6 @@ protected boolean executeProgram(int dataSetNumber) { } - //determine whether the Problem is configured to use a sandbox - usingSandbox = isUsingSandbox(); - // we do not do sandboxes or interactive for test runs if(!isTestRunOnly()) { if(usingSandbox) { @@ -2041,7 +2054,7 @@ protected boolean executeProgram(int dataSetNumber) { //We do NOT disable the ExecutionTimer here for sandbox mode as the original comment suggests. // The reason is, we use the ExecutionTimer as a watchdog in the event that the sandbox script hangs for some reason. - // We do bump the time up limit up a bit, though, so it doesn't prematurely terminate the run. JB 05/03/2023 + // We do bump the time up limit up a bit (see delaySecs above), though, so it doesn't prematurely terminate the run. JB 05/03/2023 // //if a sandbox is being used, disable both the ExecutionTimer's actionPerformed() ability to stop the process // as well as the TimerTask's ability to stop the process @@ -2105,19 +2118,15 @@ public void run() { } }; - //set the TLE kill task delay to the number of milliseconds allowed by the problem - long delay = problem.getTimeOutInSeconds() * 1000 ; - - if(usingSandbox) { - log.info ("adding " + SANDBOX_EXTRA_KILLTIME_MS + " msec delay to TLE-Timer for sandbox"); - delay += SANDBOX_EXTRA_KILLTIME_MS; - } + // this is how long the timers are set for. If running in a sandbox, TLE is handled by the sandbox, + // and this time represents wall time pc2 will wait. If not in a sandbox, this is the problem time limit. + long delayMs = delaySecs * 1000; //schedule the TLE kill task with the Timer -- but only for judged runs (i.e., non-team runs) // and only when we're not using a sandbox (which will handle time limit within the sandbox) if (autoStop) { - log.info ("scheduling kill task with TLE-Timer with " + delay + " msec delay"); - timeLimitKillTimer.schedule(task, delay); + log.info ("scheduling kill task with TLE-Timer with " + delayMs + " msec delay"); + timeLimitKillTimer.schedule(task, delayMs); } // TODO: Future investigation: The IOCollectors are probably not needed for interactive problems since @@ -2226,7 +2235,8 @@ public void run() { executionData.setExecuteExitValue(exitCode); executionData.setExecuteTimeMS(getExecutionTimeInMSecs()); - boolean runTimeLimitWasExceeded = getExecutionTimeInMSecs() > problem.getTimeOutInSeconds()*1000 ; + //delaySecs represents wall time for sandbox submissions, and cpu time for non-sandbox submissions. + boolean runTimeLimitWasExceeded = getExecutionTimeInMSecs() > delayMs ; executionData.setRunTimeLimitExceeded(runTimeLimitWasExceeded); /** diff --git a/src/edu/csus/ecs/pc2/core/model/ContestInformation.java b/src/edu/csus/ecs/pc2/core/model/ContestInformation.java index 0338325e3..d1dae1578 100644 --- a/src/edu/csus/ecs/pc2/core/model/ContestInformation.java +++ b/src/edu/csus/ecs/pc2/core/model/ContestInformation.java @@ -1,4 +1,4 @@ -// Copyright (C) 1989-2023 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. +// Copyright (C) 1989-2024 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. package edu.csus.ecs.pc2.core.model; import java.io.Serializable; @@ -15,14 +15,14 @@ /** * Contest-wide Information/settings. - * + * * @author pc2@ecs.csus.edu */ public class ContestInformation implements Serializable{ /** - * + * */ private static final long serialVersionUID = -7333255582657988200L; @@ -31,36 +31,42 @@ public class ContestInformation implements Serializable{ */ public static final int DEFAULT_FREEZE_MINUTES = 60; + /** + * Default Sandbox grace time constants + */ + public static final int SANDBOX_GRACE_TIME_SECS = 1; + public static final int SANDBOX_INTERACTIVE_MULTIPLIER = 3; + private String contestTitle = "Programming Contest"; private String contestURL; - + private TeamDisplayMask teamDisplayMode = TeamDisplayMask.LOGIN_NAME_ONLY; private String judgesDefaultAnswer = "No response, read problem statement"; - + private boolean preliminaryJudgementsUsedByBoard = false; private boolean preliminaryJudgementsTriggerNotifications = false; - + private boolean sendAdditionalRunStatusInformation = false; - + private String judgeCDPBasePath = null; - + private String adminCDPBasePath = null; - + /** * Test mode allow run submission with override elapsed time. - * + * */ private boolean ccsTestMode = false; - + //Shadow Mode settings private boolean shadowMode = false; private String primaryCCS_URL = null; private String primaryCCS_user_login = ""; private String primaryCCS_user_pw = ""; private String lastShadowEventID = ""; - + /** * Global setting for maximum output allowed by a submission. * Note that this value can be overridden on a per-problem basis. @@ -69,35 +75,41 @@ public class ContestInformation implements Serializable{ /** * This is a list of the judgement notification end of contest control settings. - * + * */ private JudgementNotificationsList judgementNotificationsList = new JudgementNotificationsList(); - + private String externalYamlPath = null; - + private String rsiCommand = null; - + private int lastRunNumberSubmitted = 0; - + /** * Memory Limit for team's solution (application). */ private int memoryLimitInMeg = 0; - + /** * Sandbox application or application command line. */ private String sandboxCommandLine = ""; - + /** * Display string for team display on standings. - * + * * @see ScoreboardVariableReplacer#substituteDisplayNameVariables(String, Account, Group) */ private String teamScoreboardDisplayFormat = ScoreboardVariableReplacer.TEAM_NAME; /** - * + * Grace time constants to adjust Executable timers + */ + private int sandboxGraceTimeSecs = SANDBOX_GRACE_TIME_SECS; + private int sandboxInteractiveGraceMultiplier = SANDBOX_INTERACTIVE_MULTIPLIER; + + /** + * * @author pc2@ecs.csus.edu */ public enum TeamDisplayMask { @@ -114,7 +126,7 @@ public enum TeamDisplayMask { */ DISPLAY_NAME_ONLY, /** - * name and number, teamN Johns Hopkins Team 1. + * name and number, teamN Johns Hopkins Team 1. */ NUMBERS_AND_NAME, /** @@ -124,15 +136,15 @@ public enum TeamDisplayMask { */ ALIAS, } - + private Properties scoringProperties = new Properties(); /** * Enable team auto registration. - * + * */ private boolean enableAutoRegistration = false; - + /** * The password type for the new passwords. */ @@ -143,20 +155,20 @@ public enum TeamDisplayMask { // * Contest Start/Date Time. // */ // private Date startDate; - + /** * The date/time when the contest is scheduled (intended) to start. * This value is null (undefined) if no scheduled start time has been set. - * This value ONLY applies BEFORE THE CONTEST STARTS; once + * This value ONLY applies BEFORE THE CONTEST STARTS; once * any "start contest" operation (e.g. pushing the "Start Button") has occurred, * this value no longer has meaning. */ private GregorianCalendar scheduledStartTime = null ; - + private boolean autoStartContest = false; private boolean autoStopContest = false ; - + /** * Scoreboard freeze time. */ @@ -167,44 +179,44 @@ public enum TeamDisplayMask { /** * Whether the contest is thawed(unfrozen). Meaning the final scoreboard * can be revealed (despite the scoreboad freeze). - * + * * Typically a contest is not thawed until the awards ceremony. */ private Date thawed = null; private boolean allowMultipleLoginsPerTeam; - - + + /** - * Load CDP samples/data in front of judges secret data. - * + * Load CDP samples/data in front of judges secret data. + * *
-     * # in yaml, do not load sample judges data 
+     * # in yaml, do not load sample judges data
      * loadSampleJudgesData : false
      * 
- * + * */ private boolean loadSampleJudgesData = true; - - + + /** * stop-on-first-failed-test-case - * + * */ private boolean stopOnFirstFailedtestCase = false; private String overrideLoadAccountsFilename = null; - + /** * Returns the date/time when the contest is scheduled (intended) to start. * This value is null if no scheduled start time has been set, - * or if the contest has already started. + * or if the contest has already started. * @see ContestTime#getContestStartTime() */ public GregorianCalendar getScheduledStartTime() { return scheduledStartTime; } - + /** * Receives a {@link GregorianCalendar} object specifying a (future) instant in time; * sets the specified date/time as the scheduled (intended) start time for the @@ -212,10 +224,10 @@ public GregorianCalendar getScheduledStartTime() { * after the contest has started, since the value of "scheduled start time" is meaningless after * the contest is under way. It is the responsibility of clients to insure * this method is only invoked with a non-null value before the contest has been started. - * + * */ public void setScheduledStartTime(GregorianCalendar newScheduledStartTime) { - scheduledStartTime = newScheduledStartTime; + scheduledStartTime = newScheduledStartTime; } public String getContestTitle() { @@ -248,7 +260,7 @@ public String getJudgesDefaultAnswer() { /** * judgesDefaultAnswer must be a non-zero length trimmed string. - * + * * @param judgesDefaultAnswer The judgesDefaultAnswer to set. */ public void setJudgesDefaultAnswer(String judgesDefaultAnswer) { @@ -256,7 +268,7 @@ public void setJudgesDefaultAnswer(String judgesDefaultAnswer) { this.judgesDefaultAnswer = judgesDefaultAnswer.trim(); } } - + public boolean isSameAs(ContestInformation contestInformation) { try { if (contestTitle == null) { @@ -309,10 +321,10 @@ public boolean isSameAs(ContestInformation contestInformation) { } if (enableAutoRegistration != contestInformation.isEnableAutoRegistration()) { return false; - } + } if (! StringUtilities.stringSame(primaryCCS_URL, contestInformation.primaryCCS_URL)) { return false; - } + } if (! StringUtilities.stringSame(primaryCCS_user_login, contestInformation.primaryCCS_user_login)) { return false; } @@ -331,9 +343,9 @@ public boolean isSameAs(ContestInformation contestInformation) { //DateUtilities.dateSame() expects Date objects but ContestInformation now maintains // scheduledStartTime (formerly "StartDate") as a GregorianCalendar; need to convert. //Also need to first check for null references (to avoid NPEs on fetch of Date from GregorianCalendar) - - //If the references to scheduledStartTime in the two ContestInfos are such that one is null and the other - // is not, the ContestInfos are not the same so return false. + + //If the references to scheduledStartTime in the two ContestInfos are such that one is null and the other + // is not, the ContestInfos are not the same so return false. // Note that "one is null and the other is not null" can be computed with the ^ (XOR) operator: // "A XOR B" = true iff A != B if (scheduledStartTime==null ^ contestInformation.getScheduledStartTime()==null) { @@ -343,12 +355,12 @@ public boolean isSameAs(ContestInformation contestInformation) { //If both are null, this test for equality passes and we fall through to other cases //If both non-null, get Dates from both and compare them if (scheduledStartTime!=null /*and therefore contestInformation.getScheduledStartTime() also != null*/) { - if (!DateUtilities.dateSame(scheduledStartTime.getTime(), + if (!DateUtilities.dateSame(scheduledStartTime.getTime(), contestInformation.getScheduledStartTime().getTime())) { return false; } - } - + } + //both scheduledStartTime and contestInformation.getScheduledStartTime() must be null (hence, "same") //continue; @@ -366,21 +378,21 @@ public boolean isSameAs(ContestInformation contestInformation) { if (!StringUtilities.stringSame(judgeCDPBasePath, contestInformation.getJudgeCDPBasePath())) { return false; } - + if (!StringUtilities.stringSame(adminCDPBasePath, contestInformation.getAdminCDPBasePath())) { return false; } if (autoStopContest != contestInformation.isAutoStopContest()) { return false; } - + if (allowMultipleLoginsPerTeam != contestInformation.isAllowMultipleLoginsPerTeam()) { return false; } - + return true; } catch (Exception e) { - e.printStackTrace(System.err); // TODO log this exception + e.printStackTrace(System.err); // TODO log this exception return false; } } @@ -395,7 +407,7 @@ public void setPreliminaryJudgementsUsedByBoard(boolean preliminaryJudgementsUse /** * The Scoring Algorithm should use this to determine whether to count preliminary judgements * as scoreable. - * + * * @return the preliminaryJudgementsUsedByBoard */ public boolean isPreliminaryJudgementsUsedByBoard() { @@ -423,7 +435,7 @@ public boolean isSendAdditionalRunStatusInformation() { public void setSendAdditionalRunStatusInformation(boolean sendAdditionalRunStatusInformation) { this.sendAdditionalRunStatusInformation = sendAdditionalRunStatusInformation; } - + public JudgementNotificationsList getJudgementNotificationsList() { return judgementNotificationsList; } @@ -431,7 +443,7 @@ public JudgementNotificationsList getJudgementNotificationsList() { public void setJudgementNotificationsList(JudgementNotificationsList judgementNotificationsList) { this.judgementNotificationsList = judgementNotificationsList; } - + public void updateJudgementNotification (NotificationSetting notificationSetting ){ judgementNotificationsList.update(notificationSetting); } @@ -442,7 +454,7 @@ public void updateJudgementNotification (NotificationSetting notificationSetting * via a call to {@link Problem#setMaxOutputFileSizeKB(int)}; note also that * the parameter for {@link Problem#setMaxOutputFileSizeKB(int)} is in KB, whereas * the value returned by THIS method is in BYTES. - * + * * @return maximum output file size in bytes. */ public long getMaxOutputSizeInBytes() { @@ -455,21 +467,21 @@ public long getMaxOutputSizeInBytes() { * Problem does NOT have a specified problem-specific output size limit. * Note also that problem-specific output size limits are specified in KB, * whereas the parameter for this method gives the (global) output size limit in BYTES. - * + * * @param maxOutputSizeInBytes - * - * @see Problem#setMaxOutputSizeKB(long) + * + * @see Problem#setMaxOutputSizeKB(long) * @see Problem#getMaxOutputSizeKB() */ public void setMaxOutputSizeInBytes(long maxOutputSizeInBytes) { this.maxOutputSizeInBytes = maxOutputSizeInBytes; } - + /** * Scoring Properties. - * + * * Minute penalties, etc. - * + * * @return */ public Properties getScoringProperties() { @@ -483,11 +495,11 @@ public void setScoringProperties(Properties scoringProperties) { public boolean isCcsTestMode() { return ccsTestMode; } - + public void setCcsTestMode(boolean ccsTestMode) { this.ccsTestMode = ccsTestMode; } - + public boolean isShadowMode() { return shadowMode; } @@ -499,9 +511,9 @@ public void setShadowMode(boolean shadowMode) { public void setRsiCommand(String rsiCommand) { this.rsiCommand = rsiCommand; } - + /** - * Returns the String representation for a "Primary CCS" URL (that is, the URL of a Remote CCS being shadowed); + * Returns the String representation for a "Primary CCS" URL (that is, the URL of a Remote CCS being shadowed); * only relevant when operating this instance of the PC2 CCS as a "Shadow CCS". * @return a String containing the URL of the Primary CCS which we're shadowing */ @@ -510,7 +522,7 @@ public String getPrimaryCCS_URL() { } /** - * Sets the String representation for a "Primary CCS" URL (that is, the URL of a Remote CCS being shadowed); + * Sets the String representation for a "Primary CCS" URL (that is, the URL of a Remote CCS being shadowed); * only relevant when operating this instance of the PC2 CCS as a "Shadow CCS". * @param primaryCCS_URL a String giving the URL of the Primary (remote) CCS (the CCS being shadowed) */ @@ -519,7 +531,7 @@ public void setPrimaryCCS_URL(String primaryCCS_URL) { } /** - * Returns a String containing the user login account to be used when connecting + * Returns a String containing the user login account to be used when connecting * to a Primary CCS (only useful when operating this instance of PC2 as a "Shadow CCS"). * @return a String containing the Primary CCS user account name */ @@ -537,7 +549,7 @@ public void setPrimaryCCS_user_login(String primaryCCS_user_login) { } /** - * Returns a String containing the password used for logging in to the + * Returns a String containing the password used for logging in to the * Primary CCS (only useful when operating this instance of PC2 as a * "Shadow CCS"). * @return a String containing a password @@ -580,39 +592,39 @@ public void setLastShadowEventID(String lastShadowEventID) { /** * Get the Run Submission Interface (RSI) command. - * + * * @return the command string to run when each run is submitted. */ public String getRsiCommand() { return rsiCommand; } - + public void setExternalYamlPath(String externalYamlPath) { this.externalYamlPath = externalYamlPath; } /** * Get the external YAML path. - * + * * @return the location of contest.yaml and problem data files. */ public String getExternalYamlPath() { return externalYamlPath; } - + public void setLastRunNumberSubmitted(int lastRunNumberSubmitted) { this.lastRunNumberSubmitted = lastRunNumberSubmitted; } - + /** * Get the last run that was sent to the RSI. - * + * * @see #getRsiCommand() * @return */ public int getLastRunNumberSubmitted() { return lastRunNumberSubmitted; } - + public boolean isEnableAutoRegistration() { return enableAutoRegistration; } @@ -632,12 +644,12 @@ public void setAutoRegistrationPasswordType(PasswordType autoRegistrationPasswor /** * Sets the contest scheduled start time from the specified Date. * Note: previously, ContestInformation stored "startDate" as an object of - * class {@link Date}. It nows stores the scheduled start time as a + * class {@link Date}. It nows stores the scheduled start time as a * {@link GregorianCalendar}; however, this method is maintained for compatibility. * The method converts the given {@link Date} into an equivalent {@link GregorianCalendar} * and invokes {@link #scheduledStartTime} with the resulting {@link GregorianCalendar} object. - * - * @param startDate - the date at which the contest is scheduled to start; + * + * @param startDate - the date at which the contest is scheduled to start; * specifying "null" as the start date causes the scheduled start time to become undefined */ public void setScheduledStartDate(Date startDate) { @@ -649,7 +661,7 @@ public void setScheduledStartDate(Date startDate) { setScheduledStartTime(newStartDate); } } - + /** * Returns a {@link Date} object representing the scheduled start time for the contest, * or null if no scheduled start time has been set. @@ -661,19 +673,19 @@ public Date getScheduledStartDate() { if (scheduledStartTime == null) { return null; } else { - return scheduledStartTime.getTime(); + return scheduledStartTime.getTime(); } } - + public boolean isAutoStartContest() { return autoStartContest; } - + public void setAutoStartContest(boolean autoStartContest) { this.autoStartContest = autoStartContest; } - + public void setAutoStopContest(boolean autoStopContest) { this.autoStopContest = autoStopContest; } @@ -681,11 +693,11 @@ public void setAutoStopContest(boolean autoStopContest) { public boolean isAutoStopContest() { return autoStopContest; } - + public String getFreezeTime() { return freezeTime; } - + public void setFreezeTime(String freezeTime) { this.freezeTime = freezeTime; } @@ -693,16 +705,16 @@ public void setFreezeTime(String freezeTime) { public String getContestShortName() { return contestShortName; } - + public void setContestShortName(String contestShortName) { this.contestShortName = contestShortName; } - + /** * Set base path for CDP/external files on judge. - * + * * This is the base path location where the problem data files are located. - * + * * @param judgeCDPBasePath */ public void setJudgeCDPBasePath(String judgeCDPBasePath) { @@ -716,11 +728,11 @@ public void setJudgeCDPBasePath(String judgeCDPBasePath) { public String getJudgeCDPBasePath() { return judgeCDPBasePath; } - + public void setAdminCDPBasePath(String adminCDPBasePath) { this.adminCDPBasePath = adminCDPBasePath; } - + /** * Get base path for CDP on Admin. */ @@ -737,20 +749,20 @@ public boolean isUnfrozen() { /** * Returns the date the was thawed, or null if not thawed. - * + * * @return Date the contest was thawed, else null */ public Date getThawed() { return thawed; } - + /** * @param thawed when/if the contest is thawed */ public void setThawed(Date date) { thawed = date; } - + /** * @param thawed whether the contest is thawed */ @@ -764,38 +776,38 @@ public void setThawed(boolean thawed) { /** * Sets the boolean flag indicating whether or not teams are allowed to have multiple simultaneous logins. - * Note that this is a GLOBAL setting, configured on the Admin's "Configure Contest>Settings" screen (or via YAML); + * Note that this is a GLOBAL setting, configured on the Admin's "Configure Contest>Settings" screen (or via YAML); * either ALL teams are allowed to have multiple simultaneous logins, or NO team is allowed to have multiple simultaneous logins. - * + * * @param allowMultipleLoginsPerTeam whether or not a team is allowed to have multiple simultaneous login sessions. */ public void setAllowMultipleLoginsPerTeam(boolean allowMultipleLoginsPerTeam) { this.allowMultipleLoginsPerTeam = allowMultipleLoginsPerTeam; } - + /** * Returns a boolean indicating whether or not the Contest Settings allow teams to have multiple simultaneous logins. * Note that this is a GLOBAL setting, configured on the Admin's "Configure Contest>Settings" screen; either ALL teams * are allowed to have multiple simultaneous logins, or NO team is allowed to have multiple simultaneous logins. - * + * * @return a boolean indicating the current "allow multiple simultaneous logins" setting for teams. */ public boolean isAllowMultipleLoginsPerTeam() { - return this.allowMultipleLoginsPerTeam; + return this.allowMultipleLoginsPerTeam; } public boolean isStopOnFirstFailedtestCase() { return stopOnFirstFailedtestCase; } - + public void setStopOnFirstFailedtestCase(boolean stopOnFirstFailedtestCase) { this.stopOnFirstFailedtestCase = stopOnFirstFailedtestCase; } - - + + /** * Returns a string which defines which fields will be dispayed on the scoreboard. - * + * * @see ScoreboardVariableReplacer#substituteDisplayNameVariables(String, Account, Group) * @return */ @@ -827,11 +839,11 @@ public String getSandboxCommandLine() { public void setSandboxCommandLine(String sandboxCommandLine) { this.sandboxCommandLine = sandboxCommandLine; } - + public String getOverrideLoadAccountsFilename() { return overrideLoadAccountsFilename; } - + public void setOverrideLoadAccountsFilename(String overrideLoadAccountsFilename) { this.overrideLoadAccountsFilename = overrideLoadAccountsFilename; } @@ -839,9 +851,25 @@ public void setOverrideLoadAccountsFilename(String overrideLoadAccountsFilename) public boolean isLoadSampleJudgesData() { return loadSampleJudgesData; } - + public void setLoadSampleJudgesData(boolean loadSampleJudgesData) { this.loadSampleJudgesData = loadSampleJudgesData; } + public int getSandboxGraceTimeSecs() { + return(sandboxGraceTimeSecs); + } + + public void setSandboxGraceTimeSecs(int nSecs) { + sandboxGraceTimeSecs = nSecs; + } + + public int getSandboxInteractiveGraceMultiplier() { + return(sandboxInteractiveGraceMultiplier); + } + + public void setSandboxInteractiveGraceMultiplier(int nSecs) { + sandboxInteractiveGraceMultiplier = nSecs; + } + } diff --git a/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java b/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java index 51553af79..e0c26a65b 100644 --- a/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java +++ b/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java @@ -290,6 +290,28 @@ private void setMemoryLimitMB(IInternalContest contest, int memoryLimitMB) { contest.updateContestInformation(contestInformation); } + private int getSandboxGraceTimeSecs(IInternalContest contest) { + ContestInformation contestInformation = contest.getContestInformation(); + return(contestInformation.getSandboxGraceTimeSecs()); + } + + private void setSandboxGraceTimeSecs(IInternalContest contest, int graceTime) { + ContestInformation contestInformation = contest.getContestInformation(); + contestInformation.setSandboxGraceTimeSecs(graceTime);; + contest.updateContestInformation(contestInformation); + } + + private int getSandboxInteractiveTimeMultiplier(IInternalContest contest) { + ContestInformation contestInformation = contest.getContestInformation(); + return(contestInformation.getSandboxInteractiveGraceMultiplier()); + } + + private void setSandboxInteractiveTimeMultiplier(IInternalContest contest, int mult) { + ContestInformation contestInformation = contest.getContestInformation(); + contestInformation.setSandboxInteractiveGraceMultiplier(mult); + contest.updateContestInformation(contestInformation); + } + private void setCcsTestMode(IInternalContest contest, boolean ccsTestMode) { ContestInformation contestInformation = contest.getContestInformation(); @@ -502,6 +524,18 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S setMemoryLimitMB(contest, globalMemoryLimit); } + int currentSandboxGraceTime = getSandboxGraceTimeSecs(contest); + Integer sandboxGraceTime = fetchIntValue(content, SANDBOX_GRACE_TIME, currentSandboxGraceTime); + if(currentSandboxGraceTime != sandboxGraceTime) { + setSandboxGraceTimeSecs(contest, sandboxGraceTime); + } + + int currentSandboxIntMult = getSandboxInteractiveTimeMultiplier(contest); + Integer sandboxIntMult = fetchIntValue(content, SANDBOX_INTERACTIVE_GRACE_MULTIPLIER, currentSandboxIntMult); + if(currentSandboxIntMult != sandboxIntMult) { + setSandboxInteractiveTimeMultiplier(contest, sandboxIntMult); + } + for (String line : yamlLines) { if (line.startsWith(CLICS_CONTEST_NAME + DELIMIT) || line.startsWith(CONTEST_NAME_KEY + DELIMIT)) { setTitle(contest, unquoteAll(line.substring(line.indexOf(DELIMIT) + 1).trim())); diff --git a/src/edu/csus/ecs/pc2/imports/ccs/IContestLoader.java b/src/edu/csus/ecs/pc2/imports/ccs/IContestLoader.java index 2cc905732..acfee3eb2 100644 --- a/src/edu/csus/ecs/pc2/imports/ccs/IContestLoader.java +++ b/src/edu/csus/ecs/pc2/imports/ccs/IContestLoader.java @@ -234,6 +234,10 @@ public interface IContestLoader { String LOAD_ACCOUNTS_FILE_KEY = "load-accounts-file"; + String SANDBOX_GRACE_TIME = "sandbox-grace-time-secs"; + + String SANDBOX_INTERACTIVE_GRACE_MULTIPLIER = "sandbox-interactive-time-multiplier"; + /** * * @see ScoreboardVariableReplacer#substituteDisplayNameVariables(String, IInternalContest, edu.csus.ecs.pc2.core.model.Account) diff --git a/src/edu/csus/ecs/pc2/ui/ContestInformationPane.java b/src/edu/csus/ecs/pc2/ui/ContestInformationPane.java index e173da27b..c59b0b637 100644 --- a/src/edu/csus/ecs/pc2/ui/ContestInformationPane.java +++ b/src/edu/csus/ecs/pc2/ui/ContestInformationPane.java @@ -46,8 +46,10 @@ import javax.swing.border.EmptyBorder; import javax.swing.border.LineBorder; import javax.swing.border.TitledBorder; + import edu.csus.ecs.pc2.core.CommandVariableReplacer; import edu.csus.ecs.pc2.core.IInternalController; +import edu.csus.ecs.pc2.core.StringUtilities; import edu.csus.ecs.pc2.core.model.ContestInformation; import edu.csus.ecs.pc2.core.model.ContestInformation.TeamDisplayMask; import edu.csus.ecs.pc2.core.model.ContestInformationEvent; @@ -57,56 +59,56 @@ /** * Contest Information edit/update Pane. - * + * * This pane displays and allows updating of Contest Information Settings. - * The pane uses a vertical BoxLayout to display a collection of settings sub-panes. Each settings sub-pane is a singleton + * The pane uses a vertical BoxLayout to display a collection of settings sub-panes. Each settings sub-pane is a singleton * which is constructed by a getter method. Each getter returns a self-contained pane (including that each returned pane * has a layout manager controlling how things are laid out within that pane and also has size and alignment * constraints defining how the components within that pane are managed by the layout manager for the pane). * Each sub-pane also has a CompoundBorder consisting of a {@link TitledBorder} compounded with a "margin border" * (an {@link EmptyBorder} with {@link Insets}); this provides an offset for each sub-pane within the outer pane. - * - * Method {@link #initialize()}, which is invoked whenever this ContestInformationPane is instantiated, adds two + * + * Method {@link #initialize()}, which is invoked whenever this ContestInformationPane is instantiated, adds two * components to *this* pane: a {@link JScrollPane} containing a "center pane" * (returned by {@link #getCenterPane()}), plus a button bar. The sub-panes displaying the Contest Information * Settings are added to the center pane (within the scroll pane) in the method {@link #getCenterPane()}. - * - * Method {@link #setContestAndController()} (which is expected to be invoked by any client instantiating - * this ContestInformationPane class) invokes method {@link #populateGUI()}, which in turn invokes + * + * Method {@link #setContestAndController()} (which is expected to be invoked by any client instantiating + * this ContestInformationPane class) invokes method {@link #populateGUI()}, which in turn invokes * {@link IInternalController.getContest().getContestInformation()} to obtain the current contest information settings from * the server; it then uses the returned values to initialize the GUI display settings. - * - * Each active component in the pane (including its sub-panes) has a listener attached to it. Whenever a component + * + * Each active component in the pane (including its sub-panes) has a listener attached to it. Whenever a component * is changed (key typed/release, checkbox checked, button pushed, etc.) it invokes method {@link #enableUpdateButton()}. * This method (despite its name) doesn't actually necessarily *enable* the Update button; rather, it invokes {@link #getFromFields()} - * to obtain the data currently displayed in the GUI fields and compares it with the current contest information settings. - * If they DIFFER then the Update and Cancel buttons are enabled. Subsequently pressing the Update button invokes - * {@link #updateContestInformation()}, which (again) invokes {@link #getFromFields()} to fetch the GUI settings and then + * to obtain the data currently displayed in the GUI fields and compares it with the current contest information settings. + * If they DIFFER then the Update and Cancel buttons are enabled. Subsequently pressing the Update button invokes + * {@link #updateContestInformation()}, which (again) invokes {@link #getFromFields()} to fetch the GUI settings and then * invokes {@link IInternalController.updateContestInformation(contestInformation)} to save the new GUI information in the * local controller (which presumably responds by saving it on the server). - * + * * Developer's Notes: - * + * * To add a new sub-pane to this ContestInformationPane, define a getter method (e.g. getNewPane()) * which returns the new pane as an instance of {@link JPanel}, and add a call centerPane.add(getNewPane()) * in method {@link #getCenterPane()}. - * + * * To add a new {@link JComponent} to an *existing* sub-pane, first create an accessor which creates the new component - * (for example, getNewComponent()), then go to the getter method for the sub-pane to which the new component - * is to be added (for example, {@link #getCCSTestModePane()}) and add to the body of that - * method an "add" statement which calls the new getter (for example, in the body of {@link #getCCSTestModePane()} you might add + * (for example, getNewComponent()), then go to the getter method for the sub-pane to which the new component + * is to be added (for example, {@link #getCCSTestModePane()}) and add to the body of that + * method an "add" statement which calls the new getter (for example, in the body of {@link #getCCSTestModePane()} you might add * ccsTestModePane.add(getNewComponent()). Note that the new component could be either an individual component * (such as a JLabel or JCheckBox) or a {@link JPanel} which itself contains sub-components. - * + * * Note that you may (probably will) have to adjust the maximum, minimum, and preferred sizes of the pane to which the * new component is being added in order to accommodate the new component in the layout. Note also that you must include * the necessary size and alignment attributes in any new component being added. - * + * * Note also that if you add new information to the GUI, you must update {@link #getFromFields()} to fetch the new information from * the GUI fields and save it, and you must update method {@link ContestInformation#isSameAs(ContestInformation)} to include * a check of the new information. - * - * + * + * * @author pc2@ecs.csus.edu */ public class ContestInformationPane extends JPanePlugin { @@ -160,7 +162,7 @@ public class ContestInformationPane extends JPanePlugin { private JTextField runSubmissionInterfaceCommandTextField = null; private JLabel runSubmissionInterfaceLabel = null; - + private JTextField startTimeTextField; private JLabel startTimeLabel; @@ -180,7 +182,7 @@ public class ContestInformationPane extends JPanePlugin { private JPanel judgesDefaultAnswerPane; private JPanel judgingOptionsPane; - + private ScoringPropertiesPane scoringPropertiesPane; private JPanel teamSettingsPane; @@ -233,17 +235,25 @@ public class ContestInformationPane extends JPanePlugin { private JLabel teamScoreboardDisplayFormatLabel; private JLabel teamDisplayFormatWhatsThisButton; - + + private JPanel executeTimingPane; + private JLabel sandboxGraceTimeLabel; + private JTextField sandboxGraceTimeTextField; + private JLabel sandboxInteractiveMultiplierLabel; + private JTextField sandboxInteractiveMultiplierTextField; + private JLabel sandboxInteractiveMultiplerWhatsThisButton; + private JLabel sandboxGraceTimeWhatsThisButton; + // private JTextField textfieldPrimaryCCSURL; // // private JTextField textfieldPrimaryCCSLogin; // // private JTextField textfieldPrimaryCCSPasswd; - + /** * This method initializes this Contest Information Pane - * + * */ public ContestInformationPane() { super(); @@ -252,22 +262,22 @@ public ContestInformationPane() { /** * This method initializes this - * + * */ private void initialize() { this.setLayout(new BorderLayout()); this.setSize(new Dimension(900, 700)); - + //put the center pane in a scrollpane so the user can access it without expanding the window JScrollPane sp = new JScrollPane(getCenterPane()); this.add(sp,BorderLayout.CENTER); - + this.add(getButtonPanel(), java.awt.BorderLayout.SOUTH); } /** * This method initializes buttonPanel - * + * * @return javax.swing.JPanel */ private JPanel getButtonPanel() { @@ -285,98 +295,98 @@ private JPanel getButtonPanel() { /** * This method initializes centerPane - the central pane containing the Contest Information Settings * and control components. - * + * * @return javax.swing.JPanel */ private JPanel getCenterPane() { if (centerPane == null) { - + centerPane = new JPanel(); centerPane.setToolTipText(""); centerPane.setLayout(new BoxLayout(centerPane,BoxLayout.Y_AXIS)); - + //contents of the pane: - + centerPane.add(Box.createVerticalStrut(15)); centerPane.add(getContestSettingsPane()) ; centerPane.add(Box.createVerticalStrut(15)); - + centerPane.add(getJudgingSettingsPane(),null); centerPane.add(Box.createVerticalStrut(15)); - + centerPane.add(getTeamSettingsPane()); centerPane.add(Box.createVerticalStrut(15)); - + centerPane.add(getRemoteCCSSettingsPane()); centerPane.add(Box.createVerticalStrut(15)); - + } return centerPane; } private Component getRemoteCCSSettingsPane() { if (remoteCCSSettingsPane == null) { - + remoteCCSSettingsPane = new JPanel(); remoteCCSSettingsPane.setAlignmentX(LEFT_ALIGNMENT); remoteCCSSettingsPane.setMaximumSize(new Dimension(900, 250)); remoteCCSSettingsPane.setMinimumSize(new Dimension(900, 250)); remoteCCSSettingsPane.setPreferredSize(new Dimension(900,250)); - - + + if (showPaneOutlines) { - + TitledBorder titleBorder = new TitledBorder("Remote CCS Settings "); titleBorder.setBorder(lineBorderBlue2px); remoteCCSSettingsPane.setBorder(new CompoundBorder(margin,titleBorder)); - + } else { remoteCCSSettingsPane.setBorder(new EmptyBorder(2,2,2,2)); } - + remoteCCSSettingsPane.setLayout(new BoxLayout(remoteCCSSettingsPane, BoxLayout.Y_AXIS)); //the contents of the pane: - + remoteCCSSettingsPane.add(Box.createVerticalStrut(15)); - + remoteCCSSettingsPane.add(getCCSTestModePane(),JComponent.LEFT_ALIGNMENT); remoteCCSSettingsPane.add(Box.createVerticalStrut(15)); - + remoteCCSSettingsPane.add(getShadowSettingsPane(),JComponent.LEFT_ALIGNMENT); } return remoteCCSSettingsPane; - + } private JPanel getCCSTestModePane() { if (ccsTestModePane == null) { - + ccsTestModePane = new JPanel(); ccsTestModePane.setLayout(new FlowLayout(FlowLayout.LEFT)); ccsTestModePane.setPreferredSize(new Dimension(700, 80)); ccsTestModePane.setMaximumSize(new Dimension(700, 80)); ccsTestModePane.setMinimumSize(new Dimension(700, 80)); - + TitledBorder tb = BorderFactory.createTitledBorder("CCS Test Mode"); ccsTestModePane.setBorder(new CompoundBorder(margin,tb)); - ccsTestModePane.setAlignmentX(LEFT_ALIGNMENT); - + ccsTestModePane.setAlignmentX(LEFT_ALIGNMENT); + //the contents of the pane: - + ccsTestModePane.add(getCcsTestModeCheckbox(), null); ccsTestModePane.add(getHorizontalStrut_2()); - + ccsTestModePane.add(getRunSubmissionCommandPane(),null); - + } return ccsTestModePane; - + } @@ -384,15 +394,15 @@ private JPanel getRunSubmissionCommandPane() { if (runSubmissionCommandPane == null) { runSubmissionCommandPane = new JPanel(); runSubmissionCommandPane.setMaximumSize(new Dimension(500, 20)); - + runSubmissionInterfaceLabel = new JLabel(); runSubmissionInterfaceLabel.setHorizontalTextPosition(SwingConstants.TRAILING); runSubmissionInterfaceLabel.setText("Run Submission Command: "); runSubmissionInterfaceLabel.setToolTipText("The command used to submit to a remote CCS"); runSubmissionInterfaceLabel.setHorizontalAlignment(SwingConstants.RIGHT); - + //the contents of the pane: - + runSubmissionCommandPane.add(runSubmissionInterfaceLabel, null); runSubmissionCommandPane.add(getRunSubmissionInterfaceCommandTextField(), null); @@ -402,9 +412,9 @@ private JPanel getRunSubmissionCommandPane() { private Component getScoreboardFreezePane() { if (scoreboardFreezePane == null) { - + scoreboardFreezePane = new JPanel(); - + scoreboardFreezePane.add(getContestFreezeLengthLabel(),null); scoreboardFreezePane.add(getContestFreezeLengthtextField()); @@ -421,9 +431,90 @@ private Component getScheduledStartTimePane() { return scheduledStartTimePane; } + private Component getExecuteTimingPane() { + if(executeTimingPane == null) { + executeTimingPane = new JPanel(); + executeTimingPane.setLayout(new FlowLayout(FlowLayout.LEFT)); + executeTimingPane.add(getSandboxGraceTimeLabel()); + executeTimingPane.add(getSandboxGraceTimeTextField()); + executeTimingPane.add(getSandboxGraceTimeWhatsThisButton()); + executeTimingPane.add(Box.createRigidArea(new Dimension(30, 0))); + executeTimingPane.add(getSandboxInteractiveMultiplierLabel()); + executeTimingPane.add(getSandboxInteractiveMultiplierTextField()); + executeTimingPane.add(getSandboxInteractiveMultiplerWhatsThisButton()); + } + return(executeTimingPane); + } + + private JLabel getSandboxGraceTimeLabel() { + if (sandboxGraceTimeLabel == null) { + + sandboxGraceTimeLabel = new JLabel(); + sandboxGraceTimeLabel.setText("Sandbox Grace Time added (seconds): "); + sandboxGraceTimeLabel.setHorizontalTextPosition(SwingConstants.TRAILING); + sandboxGraceTimeLabel.setHorizontalAlignment(SwingConstants.RIGHT); + } + return sandboxGraceTimeLabel; + } + + /** + * This method initializes sandboxGraceTimeTextField + * + * @return javax.swing.JTextField + */ + private JTextField getSandboxGraceTimeTextField() { + if (sandboxGraceTimeTextField == null) { + + sandboxGraceTimeTextField = new JTextField(4); + + sandboxGraceTimeTextField.setDocument(new IntegerDocument()); + + sandboxGraceTimeTextField.addKeyListener(new java.awt.event.KeyAdapter() { + @Override + public void keyReleased(java.awt.event.KeyEvent e) { + enableUpdateButton(); + } + }); + } + return sandboxGraceTimeTextField; + } + + private JLabel getSandboxInteractiveMultiplierLabel() { + if (sandboxInteractiveMultiplierLabel == null) { + + sandboxInteractiveMultiplierLabel = new JLabel(); + sandboxInteractiveMultiplierLabel.setText("Interactive problem time multiplier: "); + sandboxInteractiveMultiplierLabel.setHorizontalTextPosition(SwingConstants.TRAILING); + sandboxInteractiveMultiplierLabel.setHorizontalAlignment(SwingConstants.RIGHT); + } + return sandboxInteractiveMultiplierLabel; + } + + /** + * This method initializes sandboxInteractiveMultiplierTextField + * + * @return javax.swing.JTextField + */ + private JTextField getSandboxInteractiveMultiplierTextField() { + if (sandboxInteractiveMultiplierTextField == null) { + + sandboxInteractiveMultiplierTextField = new JTextField(4); + + sandboxInteractiveMultiplierTextField.setDocument(new IntegerDocument()); + + sandboxInteractiveMultiplierTextField.addKeyListener(new java.awt.event.KeyAdapter() { + @Override + public void keyReleased(java.awt.event.KeyEvent e) { + enableUpdateButton(); + } + }); + } + return sandboxInteractiveMultiplierTextField; + } + private JLabel getContestFreezeLengthLabel() { if (contestFreezeLengthLabel == null) { - + contestFreezeLengthLabel = new JLabel(); contestFreezeLengthLabel.setText("Scoreboard Freeze Length (hh:mm:ss) "); contestFreezeLengthLabel.setHorizontalTextPosition(SwingConstants.TRAILING); @@ -436,18 +527,18 @@ private Component getContestSettingsPane() { if (contestSettingsPane == null) { contestSettingsPane = new JPanel(); contestSettingsPane.setLayout(new FlowLayout(FlowLayout.LEFT)); - contestSettingsPane.setMinimumSize(new Dimension(700, 120)); - contestSettingsPane.setMaximumSize(new Dimension(700, 120)); - contestSettingsPane.setPreferredSize(new Dimension(700,120)); + contestSettingsPane.setMinimumSize(new Dimension(900, 190)); + contestSettingsPane.setMaximumSize(new Dimension(900, 190)); + contestSettingsPane.setPreferredSize(new Dimension(900,190)); contestSettingsPane.setAlignmentX(LEFT_ALIGNMENT); if (showPaneOutlines) { - + TitledBorder titleBorder = new TitledBorder("Contest Settings"); titleBorder.setBorder(lineBorderBlue2px); - + contestSettingsPane.setBorder(new CompoundBorder(margin,titleBorder)); - + } else { contestSettingsPane.setBorder(new EmptyBorder(2, 2, 2, 2)); } @@ -458,6 +549,8 @@ private Component getContestSettingsPane() { contestSettingsPane.add(getScheduledStartTimePane()); + contestSettingsPane.add(getExecuteTimingPane()); + contestSettingsPane.add(getScoreboardFreezePane()); contestSettingsPane.add(getHorizontalStrut_3()); @@ -469,9 +562,9 @@ private Component getContestSettingsPane() { private JPanel getContestTitlePane() { if (contestTitlePane == null) { - + contestTitlePane = new JPanel(); - + contestTitlePane.add(getContestTitleLabel()); contestTitlePane.add(getContestTitleTextField(), null); @@ -480,9 +573,9 @@ private JPanel getContestTitlePane() { } private JLabel getContestTitleLabel() { - + if (contestTitleLabel == null) { - + contestTitleLabel = new JLabel("Contest title: "); } return contestTitleLabel; @@ -495,38 +588,38 @@ private JLabel getContestTitleLabel() { */ private JPanel getJudgingSettingsPane() { if (judgeSettingsPane == null) { - + judgeSettingsPane = new JPanel(); - + judgeSettingsPane.setAlignmentX(LEFT_ALIGNMENT); - judgeSettingsPane.setMaximumSize(new Dimension(800, 425)); - judgeSettingsPane.setMinimumSize(new Dimension(800, 425)); - judgeSettingsPane.setPreferredSize(new Dimension(800,375)); + judgeSettingsPane.setMaximumSize(new Dimension(900, 425)); + judgeSettingsPane.setMinimumSize(new Dimension(900, 425)); + judgeSettingsPane.setPreferredSize(new Dimension(900,375)); if (showPaneOutlines) { - + TitledBorder titleBorder = new TitledBorder("Judging Settings"); titleBorder.setBorder(lineBorderBlue2px); - + judgeSettingsPane.setBorder(new CompoundBorder(margin,titleBorder)); } else { judgeSettingsPane.setBorder(new EmptyBorder(2,2,2,2)); } - + judgeSettingsPane.setLayout(new FlowLayout((FlowLayout.LEFT))); - + //the contents of the pane: - + judgeSettingsPane.add(Box.createVerticalStrut(15)); judgeSettingsPane.add(getTeamInformationDisplaySettingsPane(), LEFT_ALIGNMENT); - + judgeSettingsPane.add(getJudgesDefaultAnswerPane(),LEFT_ALIGNMENT); - + judgeSettingsPane.add(getJudgingOptionsPane(),LEFT_ALIGNMENT); - + judgeSettingsPane.add(getScoringPropertiesPane(),LEFT_ALIGNMENT); - + judgeSettingsPane.add(Box.createHorizontalStrut(20)); } @@ -535,14 +628,14 @@ private JPanel getJudgingSettingsPane() { private Component getTeamSettingsPane() { if (teamSettingsPane == null ) { - + teamSettingsPane = new JPanel(); - teamSettingsPane.setMaximumSize(new Dimension(800, 120)); - teamSettingsPane.setPreferredSize(new Dimension(800,120)); - teamSettingsPane.setAlignmentX(LEFT_ALIGNMENT); + teamSettingsPane.setMaximumSize(new Dimension(900, 120)); + teamSettingsPane.setPreferredSize(new Dimension(900,120)); + teamSettingsPane.setAlignmentX(LEFT_ALIGNMENT); if (showPaneOutlines) { - + TitledBorder titleBorder = new TitledBorder("Team Settings"); titleBorder.setBorder(lineBorderBlue2px); @@ -551,11 +644,11 @@ private Component getTeamSettingsPane() { } else { teamSettingsPane.setBorder(new EmptyBorder(2,2,2,2)); } - + teamSettingsPane.setLayout(new FlowLayout(FlowLayout.LEFT)); //contents of the pane: - + teamSettingsPane.add(getMaxOutputSizeLabel(), null); teamSettingsPane.add(getMaxOutputSizeInKTextField(), null); teamSettingsPane.add(getRigidArea1()); @@ -570,9 +663,9 @@ private JPanel getTeamScoreboardDisplayFormatPane() { if (teamScoreboardDisplayFormatPane==null) { teamScoreboardDisplayFormatPane = new JPanel(); - + //contents of the pane: - + teamScoreboardDisplayFormatPane.add(getTeamScoreboardDisplayFormatLabel()); teamScoreboardDisplayFormatPane.add(getTeamScoreboardDisplayFormatTextfield()); teamScoreboardDisplayFormatPane.add(getTeamScoreboardDisplayFormatWhatsThisButton()); @@ -581,7 +674,7 @@ private JPanel getTeamScoreboardDisplayFormatPane() { } private JLabel getTeamScoreboardDisplayFormatWhatsThisButton() { - + if (teamDisplayFormatWhatsThisButton == null) { Icon questionIcon = UIManager.getIcon("OptionPane.questionIcon"); if (questionIcon == null || !(questionIcon instanceof ImageIcon)) { @@ -628,11 +721,11 @@ public void mousePressed(MouseEvent e) { + "\n {:sitenumber} -- the PC^2 site number (in a multi-site contest) to which the team logs in (e.g., \"1\" or \"5\")" // + "\n {:countrycode} -- the ISO Country Code associated with the team (e.g. \"CAN\" or \"USA\")" // + "\n {:externalid} -- the ICPC CMS id number (if any) associated with the team (e.g., \"309407\")" // - + + "\n\nSo for example a display format string like \"{:teamname} ({:shortschoolname}) might display the following on the scoreboard:" // + "\n Hot Coders (CSUS) " // + "\n(Notice the addition of the literal parentheses around the short school name.)" // - + + "\n\nSubstitution values depend on the corresponding data having been loaded into the PC^2 Server; if there is no value defined for a" // + "\nspecified substitution string then the substitution string itself appears in the result." + " If the defined value is null or empty then an empty string appears in the result." @@ -642,8 +735,9 @@ public void mousePressed(MouseEvent e) { private JTextField getTeamScoreboardDisplayFormatTextfield() { if (teamScoreboardDisplayFormatTextfield==null) { teamScoreboardDisplayFormatTextfield = new JTextField("Undefined",30); - + teamScoreboardDisplayFormatTextfield.addKeyListener(new java.awt.event.KeyAdapter() { + @Override public void keyReleased(java.awt.event.KeyEvent e) { enableUpdateButton(); } @@ -661,7 +755,7 @@ private Component getTeamScoreboardDisplayFormatLabel() { private JLabel getMaxOutputSizeLabel() { if (labelMaxOutputSize == null) { - + labelMaxOutputSize = new JLabel(); labelMaxOutputSize.setHorizontalAlignment(SwingConstants.RIGHT); labelMaxOutputSize.setBorder(new EmptyBorder(0,10,5,5)); @@ -672,22 +766,22 @@ private JLabel getMaxOutputSizeLabel() { /** * This method initializes teamDisplaySettingPane - * + * * @return javax.swing.JPanel */ private JPanel getTeamInformationDisplaySettingsPane() { if (teamInformationDisplaySettingsPane == null) { - + teamInformationDisplaySettingsPane = new JPanel(); teamInformationDisplaySettingsPane.setMaximumSize(new Dimension(700, 200)); teamInformationDisplaySettingsPane.setAlignmentX(Component.LEFT_ALIGNMENT); - + teamInformationDisplaySettingsPane.setLayout(new FlowLayout(FlowLayout.LEFT)); - - teamInformationDisplaySettingsPane.setBorder(javax.swing.BorderFactory.createTitledBorder(null, + + teamInformationDisplaySettingsPane.setBorder(javax.swing.BorderFactory.createTitledBorder(null, "Team Information Displayed to Judges", javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, null, null)); - + //contents of pane: teamInformationDisplaySettingsPane.add(getDisplayNoneRadioButton(), null); teamInformationDisplaySettingsPane.add(getHorizontalStrut()); @@ -706,23 +800,23 @@ private JPanel getScoringPropertiesPane() { if (scoringPropertiesPane == null) { scoringPropertiesPane = new ScoringPropertiesPane(getUpdateButton(),getCancelButton()); - scoringPropertiesPane.setBorder(javax.swing.BorderFactory.createTitledBorder(null, "Scoring Properties", + scoringPropertiesPane.setBorder(javax.swing.BorderFactory.createTitledBorder(null, "Scoring Properties", javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, null, null)); - + } return scoringPropertiesPane; } - + private JPanel getJudgingOptionsPane() { if (judgingOptionsPane == null) { - + judgingOptionsPane = new JPanel(); - + judgingOptionsPane.setLayout(new BoxLayout(judgingOptionsPane,BoxLayout.Y_AXIS)); - judgingOptionsPane.setBorder(javax.swing.BorderFactory.createTitledBorder(null, "Judging Options", + judgingOptionsPane.setBorder(javax.swing.BorderFactory.createTitledBorder(null, "Judging Options", javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, null, null)); judgingOptionsPane.add(getJCheckBoxShowPreliminaryOnBoard(), LEFT_ALIGNMENT); @@ -735,19 +829,19 @@ private JPanel getJudgingOptionsPane() { private JPanel getJudgesDefaultAnswerPane() { if (judgesDefaultAnswerPane == null) { - + judgesDefaultAnswerPane = new JPanel(); judgesDefaultAnswerPane.setMaximumSize(new Dimension(500, 200)); judgesDefaultAnswerPane.setAlignmentX(Component.LEFT_ALIGNMENT); - + judgesDefaultAnswerPane.setLayout(new FlowLayout(FlowLayout.LEFT)); - - judgesDefaultAnswerPane.setBorder(javax.swing.BorderFactory.createTitledBorder(null, "Judge's Default Answer", + + judgesDefaultAnswerPane.setBorder(javax.swing.BorderFactory.createTitledBorder(null, "Judge's Default Answer", javax.swing.border.TitledBorder.DEFAULT_JUSTIFICATION, javax.swing.border.TitledBorder.DEFAULT_POSITION, null, null)); //the contents of the pane: - + judgesDefaultAnswerPane.add(getJudgesDefaultAnswerTextField(), null); } @@ -761,14 +855,15 @@ private JPanel getJudgesDefaultAnswerPane() { * {@link JTextField} component on the ShadowSettingsPane, and adds an ActionListener to the Shadow Mode * checkbox on the ShadowSettingsPane. All of these listeners do the same (one) thing: invoke * {@link #enableUpdateButton()}. - * + * * @return a ShadowSettingsPane containing Shadow Mode Settings components with listeners attached to them */ private ShadowSettingsPane getShadowSettingsPane() { if (shadowSettingsPane == null) { shadowSettingsPane = new ShadowSettingsPane(); - + KeyListener keyListener = new java.awt.event.KeyAdapter() { + @Override public void keyReleased(java.awt.event.KeyEvent e) { enableUpdateButton(); } @@ -778,12 +873,13 @@ public void keyReleased(java.awt.event.KeyEvent e) { shadowSettingsPane.getRemoteCCSPasswdTextfield().addKeyListener(keyListener); ActionListener actionListener = new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { enableUpdateButton(); } }; shadowSettingsPane.getShadowModeCheckbox().addActionListener(actionListener); - + } return shadowSettingsPane; } @@ -792,10 +888,11 @@ public void actionPerformed(ActionEvent e) { private JButton getUnfreezeScoreboardButton() { if (unfreezeScoreboardButton == null) { - + unfreezeScoreboardButton = new JButton("Unfreeze Scoreboard"); unfreezeScoreboardButton.setToolTipText("Unfreezing means the final results can be released to the public via the Contest API and public html"); unfreezeScoreboardButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { String message = "Unfreezing the scoreboard is permanent (cannot be undone);" + "\nunfreezing means the final results are released for public viewing." @@ -815,6 +912,7 @@ private JTextField getContestFreezeLengthtextField() { if (contestFreezeLengthTextField == null) { contestFreezeLengthTextField = new JTextField(8); contestFreezeLengthTextField.addKeyListener(new java.awt.event.KeyAdapter() { + @Override public void keyReleased(java.awt.event.KeyEvent e) { enableUpdateButton(); } @@ -842,7 +940,7 @@ private JLabel getStartTimeLabel() { } private JCheckBox getShadowModeCheckbox() { - + if (shadowModeCheckbox==null) { shadowModeCheckbox = getShadowSettingsPane().getShadowModeCheckbox(); } @@ -850,11 +948,11 @@ private JCheckBox getShadowModeCheckbox() { } private JTextField getPrimaryCCSURLTextfield() { - + if (primaryCCSURLTextfield==null) { primaryCCSURLTextfield = getShadowSettingsPane().getRemoteCCSURLTextfield() ; } - return primaryCCSURLTextfield; + return primaryCCSURLTextfield; } private JTextField getPrimaryCCSLoginTextfield() { @@ -866,7 +964,7 @@ private JTextField getPrimaryCCSLoginTextfield() { } private JTextField getPrimaryCCSPasswdTextfield() { - + if (primaryCCSPasswdTextfield==null) { primaryCCSPasswdTextfield = getShadowSettingsPane().getRemoteCCSPasswdTextfield() ; } @@ -876,7 +974,7 @@ private JTextField getPrimaryCCSPasswdTextfield() { /** * This method initializes updateButton - * + * * @return javax.swing.JButton */ private JButton getUpdateButton() { @@ -887,6 +985,7 @@ private JButton getUpdateButton() { updateButton.setPreferredSize(new java.awt.Dimension(100, 26)); updateButton.setMnemonic(java.awt.event.KeyEvent.VK_U); updateButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { updateContestInformation(); } @@ -897,7 +996,7 @@ public void actionPerformed(java.awt.event.ActionEvent e) { /** * This method initializes contestTitleTextField - * + * * @return javax.swing.JTextField */ private JTextField getContestTitleTextField() { @@ -905,6 +1004,7 @@ private JTextField getContestTitleTextField() { contestTitleTextField = new JTextField(0); //'0' causes textfield to resize based on its data contestTitleTextField.setAlignmentX(LEFT_ALIGNMENT); contestTitleTextField.addKeyListener(new java.awt.event.KeyAdapter() { + @Override public void keyReleased(java.awt.event.KeyEvent e) { enableUpdateButton(); } @@ -918,6 +1018,7 @@ public String getPluginTitle() { return "Contest Information Pane"; } + @Override public void setContestAndController(IInternalContest inContest, IInternalController inController) { super.setContestAndController(inContest, inController); @@ -937,15 +1038,15 @@ public void setContestAndController(IInternalContest inContest, IInternalControl protected ContestInformation getFromFields() { ContestInformation newContestInformation = new ContestInformation(); ContestInformation currentContestInformation = getContest().getContestInformation(); - + //fill in the Contest URL if (currentContestInformation.getContestURL() != null) { newContestInformation.setContestURL(new String(currentContestInformation.getContestURL())); } - + //fill in the Contest Title newContestInformation.setContestTitle(getContestTitleTextField().getText()); - + //fill in the Team Display mode if (getDisplayNoneRadioButton().isSelected()) { newContestInformation.setTeamDisplayMode(TeamDisplayMask.NONE); @@ -961,17 +1062,17 @@ protected ContestInformation getFromFields() { // DEFAULT newContestInformation.setTeamDisplayMode(TeamDisplayMask.LOGIN_NAME_ONLY); } - + //fill in judging information newContestInformation.setJudgesDefaultAnswer(getJudgesDefaultAnswerTextField().getText()); newContestInformation.setPreliminaryJudgementsTriggerNotifications(getJCheckBoxShowPreliminaryOnNotifications().isSelected()); newContestInformation.setPreliminaryJudgementsUsedByBoard(getJCheckBoxShowPreliminaryOnBoard().isSelected()); newContestInformation.setSendAdditionalRunStatusInformation(getAdditionalRunStatusCheckBox().isSelected()); - + //fill in older Run Submission Interface (RSI) data newContestInformation.setRsiCommand(getRunSubmissionInterfaceCommandTextField().getText()); newContestInformation.setCcsTestMode(getCcsTestModeCheckbox().isSelected()); - + //fill in Shadow Mode information newContestInformation.setShadowMode(getShadowSettingsPane().getShadowModeCheckbox().isSelected()); newContestInformation.setPrimaryCCS_URL(getShadowSettingsPane().getRemoteCCSURLTextfield().getText()); @@ -979,7 +1080,7 @@ protected ContestInformation getFromFields() { newContestInformation.setPrimaryCCS_user_pw(getShadowSettingsPane().getRemoteCCSPasswdTextfield().getText()); // preserve last shadow event since there is no way to change it on this pane. newContestInformation.setLastShadowEventID(currentContestInformation.getLastShadowEventID()); - + //fill in additional field values String maxFileSizeString = "0" + getMaxOutputSizeInKTextField().getText(); long maximumFileSize = Long.parseLong(maxFileSizeString); @@ -990,27 +1091,31 @@ protected ContestInformation getFromFields() { //fill in values already saved, if any if (savedContestInformation != null) { newContestInformation.setJudgementNotificationsList(savedContestInformation.getJudgementNotificationsList()); - + newContestInformation.setJudgeCDPBasePath(savedContestInformation.getJudgeCDPBasePath()); newContestInformation.setScheduledStartDate(savedContestInformation.getScheduledStartDate()); - + newContestInformation.setAdminCDPBasePath(savedContestInformation.getAdminCDPBasePath()); newContestInformation.setContestShortName(savedContestInformation.getContestShortName()); newContestInformation.setExternalYamlPath(savedContestInformation.getExternalYamlPath()); - + //TODO: why is the following being done here when it is overridden below? newContestInformation.setFreezeTime(savedContestInformation.getFreezeTime()); - + newContestInformation.setLastRunNumberSubmitted(savedContestInformation.getLastRunNumberSubmitted()); newContestInformation.setAutoStartContest(savedContestInformation.isAutoStartContest()); } newContestInformation.setScoringProperties(scoringPropertiesPane.getProperties()); - + newContestInformation.setFreezeTime(contestFreezeLengthTextField.getText()); newContestInformation.setThawed(scoreboardHasBeenUnfrozen); - + + // fill in sandbox/interative grace time adjustments + newContestInformation.setSandboxGraceTimeSecs(StringUtilities.getIntegerValue("0" + getSandboxGraceTimeTextField().getText(), savedContestInformation.getSandboxGraceTimeSecs())); + newContestInformation.setSandboxInteractiveGraceMultiplier(StringUtilities.getIntegerValue("0" + this.getSandboxInteractiveMultiplierTextField().getText(), savedContestInformation.getSandboxInteractiveGraceMultiplier())); + return (newContestInformation); } @@ -1023,7 +1128,7 @@ protected void dumpProperties(String comment, Properties properties) { Set set = properties.keySet(); - String[] keys = (String[]) set.toArray(new String[set.size()]); + String[] keys = set.toArray(new String[set.size()]); Arrays.sort(keys); @@ -1050,40 +1155,45 @@ void setEnableButtons(boolean isEnabled) { private void populateGUI() { SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { ContestInformation contestInformation = getContest().getContestInformation(); - + getContestTitleTextField().setText(contestInformation.getContestTitle()); selectDisplayRadioButton(); - + getJudgesDefaultAnswerTextField().setText(contestInformation.getJudgesDefaultAnswer()); getJCheckBoxShowPreliminaryOnBoard().setSelected(contestInformation.isPreliminaryJudgementsUsedByBoard()); getJCheckBoxShowPreliminaryOnNotifications().setSelected(contestInformation.isPreliminaryJudgementsTriggerNotifications()); getAdditionalRunStatusCheckBox().setSelected(contestInformation.isSendAdditionalRunStatusInformation()); - + getMaxOutputSizeInKTextField().setText((contestInformation.getMaxOutputSizeInBytes() / 1024) + ""); getAllowMultipleTeamLoginsCheckbox().setSelected(contestInformation.isAllowMultipleLoginsPerTeam()); getTeamScoreboardDisplayFormatTextfield().setText(contestInformation.getTeamScoreboardDisplayFormat()); getContestFreezeLengthtextField().setText(contestInformation.getFreezeTime()); - + getCcsTestModeCheckbox().setSelected(contestInformation.isCcsTestMode()); getRunSubmissionInterfaceCommandTextField().setText(contestInformation.getRsiCommand()); if (contestInformation.getRsiCommand() == null || "".equals(contestInformation.getRsiCommand().trim())) { String cmd = "# /usr/local/bin/sccsrs " + CommandVariableReplacer.OPTIONS + " " + CommandVariableReplacer.FILELIST; getRunSubmissionInterfaceCommandTextField().setText(cmd); } - + getShadowModeCheckbox().setSelected(contestInformation.isShadowMode()); getPrimaryCCSURLTextfield().setText(contestInformation.getPrimaryCCS_URL()); getPrimaryCCSLoginTextfield().setText(contestInformation.getPrimaryCCS_user_login()); getPrimaryCCSPasswdTextfield().setText(contestInformation.getPrimaryCCS_user_pw()); - + //add the scheduled start time to the GUI GregorianCalendar cal = contestInformation.getScheduledStartTime(); - getStartTimeTextField().setText(getScheduledStartTimeStr(cal)); + getStartTimeTextField().setText(getScheduledStartTimeStr(cal)); getUnfreezeScoreboardButton().setSelected(contestInformation.isUnfrozen()); + + sandboxGraceTimeTextField.setText("" + contestInformation.getSandboxGraceTimeSecs()); + sandboxInteractiveMultiplierTextField.setText("" + contestInformation.getSandboxInteractiveGraceMultiplier()); + setContestInformation(contestInformation); ((ScoringPropertiesPane) getScoringPropertiesPane()).setProperties(changedScoringProperties); setEnableButtons(false); @@ -1092,12 +1202,12 @@ public void run() { }); } - + /** * Convert a GregorianCalendar date/time to a displayable string in yyyy-mm-dd:hh:mm form. */ private String getScheduledStartTimeStr(GregorianCalendar cal) { - + String retString = ""; if (cal != null) { //extract fields from input and build string @@ -1115,35 +1225,40 @@ private String getScheduledStartTimeStr(GregorianCalendar cal) { private void updateContestInformation() { ContestInformation contestInformation = getFromFields(); - + getController().updateContestInformation(contestInformation); } class ContestInformationListenerImplementation implements IContestInformationListener { + @Override public void contestInformationAdded(ContestInformationEvent event) { populateGUI(); savedContestInformation = event.getContestInformation(); } + @Override public void contestInformationChanged(ContestInformationEvent event) { populateGUI(); savedContestInformation = event.getContestInformation(); } + @Override public void contestInformationRemoved(ContestInformationEvent event) { // TODO Auto-generated method stub } + @Override public void contestInformationRefreshAll(ContestInformationEvent contestInformationEvent) { populateGUI(); savedContestInformation = getContest().getContestInformation(); - + } - + + @Override public void finalizeDataChanged(ContestInformationEvent contestInformationEvent) { // Not used } @@ -1184,7 +1299,7 @@ private void selectDisplayRadioButton() { /** * This method initializes displayNoneButton - * + * * @return javax.swing.JRadioButton */ private JRadioButton getDisplayNoneRadioButton() { @@ -1192,6 +1307,7 @@ private JRadioButton getDisplayNoneRadioButton() { displayNoneRadioButton = new JRadioButton(); displayNoneRadioButton.setText("None"); displayNoneRadioButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { // getActionCommand called with text from button // getSource returns the JRadioButton @@ -1204,7 +1320,7 @@ public void actionPerformed(java.awt.event.ActionEvent e) { /** * This method initializes displayNumbersOnlyRadioButton - * + * * @return javax.swing.JRadioButton */ private JRadioButton getDisplayNumbersOnlyRadioButton() { @@ -1212,6 +1328,7 @@ private JRadioButton getDisplayNumbersOnlyRadioButton() { displayNumbersOnlyRadioButton = new JRadioButton(); displayNumbersOnlyRadioButton.setText("Show Numbers Only"); displayNumbersOnlyRadioButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { // getActionCommand called with text from button // getSource returns the JRadioButton @@ -1224,7 +1341,7 @@ public void actionPerformed(java.awt.event.ActionEvent e) { /** * This method initializes displayNameAndNumberRadioButton - * + * * @return javax.swing.JRadioButton */ private JRadioButton getDisplayNameAndNumberRadioButton() { @@ -1232,6 +1349,7 @@ private JRadioButton getDisplayNameAndNumberRadioButton() { displayNameAndNumberRadioButton = new JRadioButton(); displayNameAndNumberRadioButton.setText("Show Number and Name"); displayNameAndNumberRadioButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { // getActionCommand called with text from button // getSource returns the JRadioButton @@ -1244,7 +1362,7 @@ public void actionPerformed(java.awt.event.ActionEvent e) { /** * This method initializes displayAliasNameRadioButton - * + * * @return javax.swing.JRadioButton */ private JRadioButton getDisplayAliasNameRadioButton() { @@ -1252,6 +1370,7 @@ private JRadioButton getDisplayAliasNameRadioButton() { displayAliasNameRadioButton = new JRadioButton(); displayAliasNameRadioButton.setText("Show Alias"); displayAliasNameRadioButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { // getActionCommand called with text from button // getSource returns the JRadioButton @@ -1264,7 +1383,7 @@ public void actionPerformed(java.awt.event.ActionEvent e) { /** * This method initializes showNamesOnlyRadioButton - * + * * @return javax.swing.JRadioButton */ private JRadioButton getDisplayNamesOnlyRadioButton() { @@ -1272,6 +1391,7 @@ private JRadioButton getDisplayNamesOnlyRadioButton() { displayNamesOnlyRadioButton = new JRadioButton(); displayNamesOnlyRadioButton.setText("Show Names only"); displayNamesOnlyRadioButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { // getActionCommand called with text from button // getSource returns the JRadioButton @@ -1284,7 +1404,7 @@ public void actionPerformed(java.awt.event.ActionEvent e) { /** * This method initializes displayNameButtonGroup - * + * * @return javax.swing.ButtonGroup */ private ButtonGroup getDisplayNameButtonGroup() { @@ -1302,7 +1422,7 @@ private ButtonGroup getDisplayNameButtonGroup() { /** * This method initializes cancelButton - * + * * @return javax.swing.JButton */ private JButton getCancelButton() { @@ -1313,6 +1433,7 @@ private JButton getCancelButton() { cancelButton.setMnemonic(KeyEvent.VK_C); cancelButton.setPreferredSize(new java.awt.Dimension(100, 26)); cancelButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { populateGUI(); } @@ -1323,7 +1444,7 @@ public void actionPerformed(java.awt.event.ActionEvent e) { /** * This method initializes JudgesDefaultAnswerTextField - * + * * @return javax.swing.JTextField */ private JTextField getJudgesDefaultAnswerTextField() { @@ -1333,6 +1454,7 @@ private JTextField getJudgesDefaultAnswerTextField() { // judgesDefaultAnswerTextField.setSize(new Dimension(280, 29)); // judgesDefaultAnswerTextField.setLocation(new Point(208, 214)); judgesDefaultAnswerTextField.addKeyListener(new java.awt.event.KeyAdapter() { + @Override public void keyReleased(java.awt.event.KeyEvent e) { enableUpdateButton(); } @@ -1343,7 +1465,7 @@ public void keyReleased(java.awt.event.KeyEvent e) { /** * This method initializes jCheckBoxShowPreliminaryOnBoard - * + * * @return javax.swing.JCheckBox */ private JCheckBox getJCheckBoxShowPreliminaryOnBoard() { @@ -1355,6 +1477,7 @@ private JCheckBox getJCheckBoxShowPreliminaryOnBoard() { jCheckBoxShowPreliminaryOnBoard.setMnemonic(KeyEvent.VK_UNDEFINED); jCheckBoxShowPreliminaryOnBoard.setText("Include Preliminary Judgements in Scoring Algorithm"); jCheckBoxShowPreliminaryOnBoard.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { enableUpdateButton(); } @@ -1365,7 +1488,7 @@ public void actionPerformed(java.awt.event.ActionEvent e) { /** * This method initializes jCheckBoxShowPreliminaryOnNotifications - * + * * @return javax.swing.JCheckBox */ private JCheckBox getJCheckBoxShowPreliminaryOnNotifications() { @@ -1378,6 +1501,7 @@ private JCheckBox getJCheckBoxShowPreliminaryOnNotifications() { jCheckBoxShowPreliminaryOnNotifications.setMnemonic(KeyEvent.VK_UNDEFINED); jCheckBoxShowPreliminaryOnNotifications.setText("Send Balloon Notifications for Preliminary Judgements"); jCheckBoxShowPreliminaryOnNotifications.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { enableUpdateButton(); } @@ -1388,7 +1512,7 @@ public void actionPerformed(java.awt.event.ActionEvent e) { /** * This method initializes additionalRunStatusCheckBox - * + * * @return javax.swing.JCheckBox */ private JCheckBox getAdditionalRunStatusCheckBox() { @@ -1399,6 +1523,7 @@ private JCheckBox getAdditionalRunStatusCheckBox() { additionalRunStatusCheckBox.setText("Send Additional Run Status Information"); additionalRunStatusCheckBox.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { enableUpdateButton(); } @@ -1422,17 +1547,18 @@ public void setContestInformation(ContestInformation contestInformation) { /** * This method initializes maxFieldSizeInKTextField - * + * * @return javax.swing.JTextField */ private JTextField getMaxOutputSizeInKTextField() { if (textfieldMaxOutputSizeInK == null) { - + textfieldMaxOutputSizeInK = new JTextField(6); - + textfieldMaxOutputSizeInK.setDocument(new IntegerDocument()); textfieldMaxOutputSizeInK.addKeyListener(new java.awt.event.KeyAdapter() { + @Override public void keyReleased(java.awt.event.KeyEvent e) { enableUpdateButton(); } @@ -1440,31 +1566,33 @@ public void keyReleased(java.awt.event.KeyEvent e) { } return textfieldMaxOutputSizeInK; } - + private JCheckBox getAllowMultipleTeamLoginsCheckbox() { if (allowMultipleTeamLoginsCheckbox==null) { allowMultipleTeamLoginsCheckbox = new JCheckBox("Allow multiple logins per team", true); allowMultipleTeamLoginsCheckbox.addActionListener (new ActionListener() { - + + @Override public void actionPerformed(ActionEvent e) { enableUpdateButton(); } }); - + } return allowMultipleTeamLoginsCheckbox ; } private JCheckBox getCcsTestModeCheckbox() { if (ccsTestModeCheckbox == null) { - + ccsTestModeCheckbox = new JCheckBox(); - + ccsTestModeCheckbox.setText("Enable CCS Test Mode"); ccsTestModeCheckbox.setToolTipText("CCS Test Mode is used to allow PC2 to forward team submissions to a remote" + " Contest Control System via the CLICS 'Run Submission Interface'"); ccsTestModeCheckbox.setMnemonic(KeyEvent.VK_T); ccsTestModeCheckbox.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { enableUpdateButton(); } @@ -1475,7 +1603,7 @@ public void actionPerformed(java.awt.event.ActionEvent e) { /** * This method initializes runSubmissionInterfaceCommandTextField - * + * * @return javax.swing.JTextField */ private JTextField getRunSubmissionInterfaceCommandTextField() { @@ -1484,6 +1612,7 @@ private JTextField getRunSubmissionInterfaceCommandTextField() { runSubmissionInterfaceCommandTextField.setMaximumSize(new Dimension(2147483647, 20)); runSubmissionInterfaceCommandTextField.setText(""); runSubmissionInterfaceCommandTextField.addKeyListener(new java.awt.event.KeyAdapter() { + @Override public void keyReleased(java.awt.event.KeyEvent e) { enableUpdateButton(); } @@ -1492,6 +1621,89 @@ public void keyReleased(java.awt.event.KeyEvent e) { return runSubmissionInterfaceCommandTextField; } + private JLabel getSandboxInteractiveMultiplerWhatsThisButton() { + + if (sandboxInteractiveMultiplerWhatsThisButton == null) { + Icon questionIcon = UIManager.getIcon("OptionPane.questionIcon"); + if (questionIcon == null || !(questionIcon instanceof ImageIcon)) { + // the current PLAF doesn't have an OptionPane.questionIcon that's an ImageIcon + sandboxInteractiveMultiplerWhatsThisButton = new JLabel(""); + sandboxInteractiveMultiplerWhatsThisButton.setForeground(Color.blue); + } else { + Image image = ((ImageIcon) questionIcon).getImage(); + sandboxInteractiveMultiplerWhatsThisButton = new JLabel(new ImageIcon(getScaledImage(image, 20, 20))); + } + + sandboxInteractiveMultiplerWhatsThisButton.setToolTipText("What's This? (click for additional information)"); + sandboxInteractiveMultiplerWhatsThisButton.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + JOptionPane.showMessageDialog(null, sandboxInteractiveMultiplierWhatsThisMessage, "About the Sandbox Interactive Multiplier", JOptionPane.INFORMATION_MESSAGE, null); + } + }); + sandboxInteractiveMultiplerWhatsThisButton.setBorder(new EmptyBorder(0, 15, 0, 0)); + } + return sandboxInteractiveMultiplerWhatsThisButton; + } + + // the string which will be displayed when the "What's This" icon in the contest settings panel is clicked (sandbox time multiplier) + private String sandboxInteractiveMultiplierWhatsThisMessage = // + "\nThe Interactive Problem Time Multiplier field allows you to specify an integer value which defines a multiplier" // + + "\nthat will be used for the problem wall time limit for interactive problems judged in a sandbox." // + + + "\n\nThis is designed to compensate for the extra wall time that can be consumed by an interactive validator and" // + + "\nthe interactive judging facility. This value does not affect the CPU time limit for the problem. The CPU time is" // + + "\nstill controlled by the sandbox and the problem's specified time limit." // + + "\n\nConsider an example where the interactive problem's time limit is 5 seconds, and the Interative Problem Time" // + + "\nMultiplier is specified as 3. This will tell PC2 to allow a maximum of 15 seconds (5*3) for a single test case of" // + + "\nthe interactive problem to complete. The sandbox will still enforce a CPU time limit of 5 seconds, so if the CPU" // + + "\ntime limit of 5 is exceeded the run will terminate at that point with TLE. If, for example, the interactive validator" // + + "\nadds an inordinate amount of overhead and the test case run takes 13 seconds (but the submission only uses" // + + "\n3 seconds of CPU time, the submission will not be terminated by PC2 permaturely." // + + " \n\n"; + + + private JLabel getSandboxGraceTimeWhatsThisButton() { + + if (sandboxGraceTimeWhatsThisButton == null) { + Icon questionIcon = UIManager.getIcon("OptionPane.questionIcon"); + if (questionIcon == null || !(questionIcon instanceof ImageIcon)) { + // the current PLAF doesn't have an OptionPane.questionIcon that's an ImageIcon + sandboxGraceTimeWhatsThisButton = new JLabel(""); + sandboxGraceTimeWhatsThisButton.setForeground(Color.blue); + } else { + Image image = ((ImageIcon) questionIcon).getImage(); + sandboxGraceTimeWhatsThisButton = new JLabel(new ImageIcon(getScaledImage(image, 20, 20))); + } + + sandboxGraceTimeWhatsThisButton.setToolTipText("What's This? (click for additional information)"); + sandboxGraceTimeWhatsThisButton.addMouseListener(new MouseAdapter() { + @Override + public void mousePressed(MouseEvent e) { + JOptionPane.showMessageDialog(null, sandboxGraceTimeWhatsThisMessage, "About the Sandbox Grace Time", JOptionPane.INFORMATION_MESSAGE, null); + } + }); + sandboxGraceTimeWhatsThisButton.setBorder(new EmptyBorder(0, 15, 0, 0)); + } + return sandboxGraceTimeWhatsThisButton; + } + + // the string which will be displayed when the "What's This" icon in the contest settings panel is clicked (sandbox grace time) + private String sandboxGraceTimeWhatsThisMessage = // + "\nThe Sandbox Grace Time field allows you to specify an integer value which defines the number of extra" // + + "\nseconds that will be used for the problem wall time limit for problems judged in a sandbox." // + + + "\n\nThis is designed to compensate for the extra wall time that can be consumed by the sandbox judging" // + + "\nfacility. This value does not affect the CPU time limit for the problem. The CPU time is still controlled" // + + "\nby the sandbox and the problem's specified time limit." // + + "\n\nConsider an example where the problem's time limit is 5 seconds, and the Sandbox Grace Time is specified as" // + + "\n2. This will tell PC2 to allow a maximum of 7 seconds (5+2) for a single test case of the problem to complete." // + + "\nThe sandbox will still enforce a CPU time limit of 5 seconds, so if the CPU time limit of 5 seconds is" // + + "\nexceeded the run will terminate at that point with TLE. If, for example, the sandbox facility adds two extra" // + + "\nseconds of overhead and the test case run takes 6 seconds (but the submission only uses 4 seconds of CPU time," // + + "\nthe submission will not be terminated by PC2 permaturely." // + + " \n\n"; + private Image getScaledImage(Image srcImg, int w, int h) { BufferedImage resizedImg = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = resizedImg.createGraphics(); From 5363556b66914c9ef2ff34d3f51bac213da1b9c2 Mon Sep 17 00:00:00 2001 From: John Buck Date: Tue, 6 Aug 2024 19:37:55 -0400 Subject: [PATCH 2/2] i_999 CI bug fixes to SubmitSampleRunsPane CI: While testing the new sandbox time grace periods, a couple of bugs were found in the SubmitSampleRunsPane (which is used to submit the judge's sample solutions by an administrator. The following were fixed/added to support SubmitSampleRuns. 1) Add alphanumeric sorting instead of pure numeric sorting for some columns since some columns may have both text and numbers in them. 2) Synchronize the add runs since we appear to get 2 runAdded notifications for each run from different worker threads. 3) Fixed bug in Filter where if a custom filter group is used alone, it would not allow you to enable filtering. --- .../pc2/core/list/AlphaNumericComparator.java | 53 +++++++++++++++ .../core/list/LabelToDoubleComparator.java | 64 +++++++++++++++++ .../core/list/StringToDoubleComparator.java | 38 ----------- src/edu/csus/ecs/pc2/core/model/Filter.java | 6 +- .../ecs/pc2/list/SubmissionSolutionList.java | 2 +- .../csus/ecs/pc2/ui/SubmitSampleRunsPane.java | 68 +++++++++++-------- 6 files changed, 162 insertions(+), 69 deletions(-) create mode 100644 src/edu/csus/ecs/pc2/core/list/AlphaNumericComparator.java create mode 100644 src/edu/csus/ecs/pc2/core/list/LabelToDoubleComparator.java delete mode 100644 src/edu/csus/ecs/pc2/core/list/StringToDoubleComparator.java diff --git a/src/edu/csus/ecs/pc2/core/list/AlphaNumericComparator.java b/src/edu/csus/ecs/pc2/core/list/AlphaNumericComparator.java new file mode 100644 index 000000000..0d0c1c51c --- /dev/null +++ b/src/edu/csus/ecs/pc2/core/list/AlphaNumericComparator.java @@ -0,0 +1,53 @@ +// Copyright (C) 1989-2024 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. +package edu.csus.ecs.pc2.core.list; + +import java.io.Serializable; +import java.util.Comparator; + +/** + * Compare the two strings as numbers + *

+ * Compare things that may be words or numbers + * + * @author John Buck + */ + +// $HeadURL$ +// $Id$ + +public class AlphaNumericComparator implements Comparator, Serializable { + + private static final long serialVersionUID = 1L; + + @Override + public int compare(String ValOne, String ValTwo) { + boolean b1Alpha = false; + boolean b2Alpha = false; + int cmpVal1 = 0; + int cmpVal2 = 0; + + try { + cmpVal1 = Integer.parseInt(ValOne); + } catch(Exception exception) { + b1Alpha = true; + } + try { + cmpVal2 = Integer.parseInt(ValTwo); + } catch(Exception exception) { + b2Alpha = true; + } + if(b1Alpha) { + // If both are alpha, then just compare the strings. + if(b2Alpha) { + return(ValOne.compareToIgnoreCase(ValTwo)); + } + // ValTwo is a number, and it comes before the alpha + return(1); + } + if(b2Alpha) { + // ValOne is a number, it comes before the alpha string + return(-1); + } + return(cmpVal1 - cmpVal2); + } +} diff --git a/src/edu/csus/ecs/pc2/core/list/LabelToDoubleComparator.java b/src/edu/csus/ecs/pc2/core/list/LabelToDoubleComparator.java new file mode 100644 index 000000000..98156d459 --- /dev/null +++ b/src/edu/csus/ecs/pc2/core/list/LabelToDoubleComparator.java @@ -0,0 +1,64 @@ +// Copyright (C) 1989-2024 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. +package edu.csus.ecs.pc2.core.list; + +import java.io.Serializable; +import java.util.Comparator; + +import javax.swing.JLabel; + +/** + * Compare the two strings as numbers + *

+ * Simply convert to integers and compare + * + * @version $Id$ + * @author John Buck + */ + +// $HeadURL$ +// $Id$ + +public class LabelToDoubleComparator implements Comparator, Serializable { + + private static final long serialVersionUID = 1L; + + @Override + public int compare(JLabel LabOne, JLabel LabTwo) { + String valOne = LabOne.getText(); + String valTwo = LabTwo.getText(); + boolean b1Alpha = false; + boolean b2Alpha = false; + double cmpVal1 = 0; + double cmpVal2 = 0; + + try { + cmpVal1 = Double.parseDouble(valOne); + } catch(Exception exception) { + b1Alpha = true; + } + try { + cmpVal2 = Double.parseDouble(valTwo); + } catch(Exception exception) { + b2Alpha = true; + } + if(b1Alpha) { + // If both are alpha, then just compare the strings. + if(b2Alpha) { + return(valOne.compareToIgnoreCase(valTwo)); + } + // ValTwo is a number, and it comes before the alpha + return(1); + } + if(b2Alpha) { + // ValOne is a number, it comes before the alpha string + return(-1); + } + double dResult = cmpVal1 - cmpVal2; + if(dResult < 0) { + return(-1); + } else if(dResult > 0) { + return(1); + } + return(0); + } +} diff --git a/src/edu/csus/ecs/pc2/core/list/StringToDoubleComparator.java b/src/edu/csus/ecs/pc2/core/list/StringToDoubleComparator.java deleted file mode 100644 index 8c2036d09..000000000 --- a/src/edu/csus/ecs/pc2/core/list/StringToDoubleComparator.java +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (C) 1989-2024 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. -package edu.csus.ecs.pc2.core.list; - -import java.io.Serializable; -import java.util.Comparator; - -/** - * Compare the two strings as numbers - *

- * Simply convert to integers and compare - * - * @version $Id$ - * @author John Buck - */ - -// $HeadURL$ -// $Id$ - -public class StringToDoubleComparator implements Comparator, Serializable { - - private static final long serialVersionUID = 1L; - - @Override - public int compare(String NumOne, String NumTwo) { - double dResult = 0; - try { - dResult = Double.parseDouble(NumOne) - Double.parseDouble(NumTwo); - } catch(Exception exception) { - return(NumOne.compareTo(NumTwo)); - } - if(dResult < 0) - return(-1); - if(dResult > 0) { - return(1); - } - return(0); - } -} diff --git a/src/edu/csus/ecs/pc2/core/model/Filter.java b/src/edu/csus/ecs/pc2/core/model/Filter.java index 639e2e69a..a789a5821 100644 --- a/src/edu/csus/ecs/pc2/core/model/Filter.java +++ b/src/edu/csus/ecs/pc2/core/model/Filter.java @@ -1289,6 +1289,9 @@ public String toString() { if (filteringGroups) { filterInfo += " groups(s)"; } + if (filteringCustom) { + filterInfo += " custom"; + } return filterInfo; } else { @@ -1373,7 +1376,8 @@ public boolean isFilterOn() { || filteringJudgements || filteringLanguages || filteringElapsedTime || filteringRunStates || filteringPermissions || thisSiteOnly - || filteringGroups; + || filteringGroups + || filteringCustom; } else { return false; } diff --git a/src/edu/csus/ecs/pc2/list/SubmissionSolutionList.java b/src/edu/csus/ecs/pc2/list/SubmissionSolutionList.java index 52efbe81e..cf3a28607 100644 --- a/src/edu/csus/ecs/pc2/list/SubmissionSolutionList.java +++ b/src/edu/csus/ecs/pc2/list/SubmissionSolutionList.java @@ -77,7 +77,7 @@ public static List getAllDirectoryEntries(String directory) { * @return the acronym, or null if the folder is non-standard */ public String getAcronymForSubmissionDirectory(String dir) { - if(submissionDirectoryToAcronym.containsKey(dir)) { + if(dir != null && submissionDirectoryToAcronym.containsKey(dir)) { return submissionDirectoryToAcronym.get(dir); } return null; diff --git a/src/edu/csus/ecs/pc2/ui/SubmitSampleRunsPane.java b/src/edu/csus/ecs/pc2/ui/SubmitSampleRunsPane.java index e8535fdd2..5d43f2e75 100644 --- a/src/edu/csus/ecs/pc2/ui/SubmitSampleRunsPane.java +++ b/src/edu/csus/ecs/pc2/ui/SubmitSampleRunsPane.java @@ -46,8 +46,8 @@ import edu.csus.ecs.pc2.core.FileUtilities; import edu.csus.ecs.pc2.core.IInternalController; import edu.csus.ecs.pc2.core.Utilities; -import edu.csus.ecs.pc2.core.list.StringToDoubleComparator; -import edu.csus.ecs.pc2.core.list.StringToNumberComparator; +import edu.csus.ecs.pc2.core.list.AlphaNumericComparator; +import edu.csus.ecs.pc2.core.list.LabelToDoubleComparator; import edu.csus.ecs.pc2.core.log.Log; import edu.csus.ecs.pc2.core.log.StaticLog; import edu.csus.ecs.pc2.core.model.ClientId; @@ -310,7 +310,9 @@ protected void updateCDPDirAndFields(String cdpConfigDir) { public SubmissionSolutionList getSubmissionSolutionList() { String cdpPath = cdpTextField.getText(); - submissionSolutionList = new SubmissionSolutionList(getContest(), cdpPath); + if(submissionSolutionList == null) { + submissionSolutionList = new SubmissionSolutionList(getContest(), cdpPath); + } return submissionSolutionList; } @@ -547,7 +549,9 @@ public void run() { private void clearSubmissionFiles() { submissionFileList = null; currentSubmission = -1; - runsAdded.clear(); + synchronized(runsAdded) { + runsAdded.clear(); + } } private void stopSubmissionTimer() { @@ -781,7 +785,7 @@ public Component prepareRenderer(TableCellRenderer renderer, int row, int column // Object[] fullColumns = { "Run Id", "Time", "Problem", "Expected", "Status", "Source", "Judge", "Language", "SubmissionSample" }; int modelRow = convertRowIndexToModel(row); - String submissionAcronym = submissionSolutionList.getAcronymForSubmissionDirectory((String)runTableModel.getValueAt(modelRow, 3)); + String submissionAcronym = getSubmissionSolutionList().getAcronymForSubmissionDirectory((String)runTableModel.getValueAt(modelRow, 3)); if(submissionAcronym == null) { c.setBackground(matchColorMaybe); @@ -897,8 +901,8 @@ public boolean isCellEditable(int row, int col) { runTable.getColumnModel().getColumn(ELAPSED_TIME_COLUMN).setCellRenderer(new LinkCellRenderer()); - StringToNumberComparator numericStringSorter = new StringToNumberComparator(); - StringToDoubleComparator doubleStringSorter = new StringToDoubleComparator(); + AlphaNumericComparator numericStringSorter = new AlphaNumericComparator(); + LabelToDoubleComparator doubleStringSorter = new LabelToDoubleComparator(); int idx = 0; @@ -1051,7 +1055,7 @@ protected Object[] buildRunRow(SubmissionSample sub, ClientId judgeId) { if(maxMS == -1) { s[idx++] = new JLabel("N/A"); } else { - s[idx++] = new JLabel(String.format("%d.%03ds", maxMS/1000, maxMS%1000)); + s[idx++] = new JLabel(String.format("%d.%03d", maxMS/1000, maxMS%1000)); } s[idx++] = sub.getSourceFile().getName(); s[idx++] = getJudgesTitle(run, judgeId, autoJudgedRun); @@ -1313,14 +1317,16 @@ public void runAdded(RunEvent event) { Run run = event.getRun(); Integer runNum = new Integer(run.getNumber()); - if(runsAdded.contains(runNum)) { - log.log(Level.WARNING, "Duplicate runAdded event for Run id " + run.getNumber() + " ignored."); - if(Utilities.isDebugMode()) { - System.out.println("Duplicate runAdded (" + run.getNumber() + ") ignored - currentSubmission #" + currentSubmission); + synchronized (runsAdded) { + if(runsAdded.contains(runNum)) { + log.log(Level.WARNING, "Duplicate runAdded event for Run id " + run.getNumber() + " ignored."); + if(Utilities.isDebugMode()) { + System.out.println("Duplicate runAdded (" + run.getNumber() + ") ignored - currentSubmission #" + currentSubmission); + } + return; } - return; + runsAdded.add(runNum); } - runsAdded.add(runNum); if(Utilities.isDebugMode()) { System.out.println("Got runAdded for run ID " + run.getNumber() + " - added to runsAdded hashset"); } @@ -1329,21 +1335,25 @@ public void runAdded(RunEvent event) { // We are only interested in runs we submitted if(run.getSubmitter().equals(me)) { - // This is the last run - it has to be the one that was just added by us - sub = submissionList.get(currentSubmission); - if(sub != null) { - stopSubmissionTimer(); - sub.setRun(run); - if(Utilities.isDebugMode()) { - System.out.println("Received runAdded currentSubmission #" + currentSubmission + " for problem " + sub.toString()); - } - updateRunRow(sub, event.getWhoModifiedRun(), true); - // setup for next submission; if last one clean things up. - currentSubmission++; - if(currentSubmission >= submissionFileList.size()) { - clearSubmissionFiles(); - } else { - submitNextSubmission(); + // Since we get run added events for each run, it's possible that the currentSubmission could be -1 + // in the event of the very last submission, since clearSubmissionFiles() below would set it to -1 + if(currentSubmission >= 0) { + // This is the last run - it has to be the one that was just added by us + sub = submissionList.get(currentSubmission); + if(sub != null) { + stopSubmissionTimer(); + sub.setRun(run); + if(Utilities.isDebugMode()) { + System.out.println("Received runAdded currentSubmission #" + currentSubmission + " for problem " + sub.toString()); + } + updateRunRow(sub, event.getWhoModifiedRun(), true); + // setup for next submission; if last one clean things up. + currentSubmission++; + if(currentSubmission >= submissionFileList.size()) { + clearSubmissionFiles(); + } else { + submitNextSubmission(); + } } } }