From 5662ca93bb8c6183595cb52aa57fe554fc0e2c36 Mon Sep 17 00:00:00 2001 From: John Buck Date: Sun, 27 Jul 2025 16:34:22 -0400 Subject: [PATCH 01/14] i_1006 Allow editing of score field on run result With the addition of Point Scoring support, it seemed logical (and necessary) to be able to edit/change the score of a judgment. This commit adds that capability. --- src/edu/csus/ecs/pc2/ui/DoubleDocument.java | 48 +++++ src/edu/csus/ecs/pc2/ui/EditRunPane.java | 190 ++++++++++++++------ 2 files changed, 185 insertions(+), 53 deletions(-) create mode 100644 src/edu/csus/ecs/pc2/ui/DoubleDocument.java diff --git a/src/edu/csus/ecs/pc2/ui/DoubleDocument.java b/src/edu/csus/ecs/pc2/ui/DoubleDocument.java new file mode 100644 index 000000000..2c5d5bae3 --- /dev/null +++ b/src/edu/csus/ecs/pc2/ui/DoubleDocument.java @@ -0,0 +1,48 @@ +// Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. +package edu.csus.ecs.pc2.ui; + +import java.awt.Toolkit; + +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.PlainDocument; + +/** + * Accept Double input only. + * + * @see javax.swing.JTextField#setDocument(Document) + * @author John Buck + */ +// $HeadURL$ +public class DoubleDocument extends PlainDocument { + + /** + * + */ + private static final long serialVersionUID = 1L; + + @Override + public void insertString(int offset, String string, AttributeSet attributes) throws BadLocationException { + + if (string != null) { + String newValue; + int length = getLength(); + if (length == 0) { + newValue = string; + } else { + String currentContent = getText(0, length); + StringBuffer currentBuffer = new StringBuffer(currentContent); + currentBuffer.insert(offset, string); + newValue = currentBuffer.toString(); + } + try { + if (!newValue.equals("")) { + Double.parseDouble(newValue); + } + super.insertString(offset, string, attributes); + } catch (NumberFormatException exception) { + Toolkit.getDefaultToolkit().beep(); + } + } + } +} diff --git a/src/edu/csus/ecs/pc2/ui/EditRunPane.java b/src/edu/csus/ecs/pc2/ui/EditRunPane.java index 3aed95ae9..b0cd6f337 100644 --- a/src/edu/csus/ecs/pc2/ui/EditRunPane.java +++ b/src/edu/csus/ecs/pc2/ui/EditRunPane.java @@ -1,8 +1,10 @@ -// Copyright (C) 1989-2023 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. +// Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. package edu.csus.ecs.pc2.ui; import java.awt.BorderLayout; +import java.awt.Dimension; import java.awt.FlowLayout; +import java.awt.Point; import java.awt.event.KeyAdapter; import java.io.File; import java.io.IOException; @@ -41,12 +43,10 @@ import edu.csus.ecs.pc2.core.model.SerializedFile; import edu.csus.ecs.pc2.core.report.ExtractRuns; import edu.csus.ecs.pc2.core.security.FileSecurityException; -import java.awt.Point; -import java.awt.Dimension; /** * Add/Edit Run Pane - * + * * @author pc2@ecs.csus.edu * @version $Id$ */ @@ -55,7 +55,7 @@ public class EditRunPane extends JPanePlugin { /** - * + * */ private static final long serialVersionUID = 8747938709622932819L; @@ -98,7 +98,7 @@ public class EditRunPane extends JPanePlugin { private JLabel statusTitleLabel = null; private JComboBox problemComboBox = null; - + private JComboBox runStatusComboBox = null; private JComboBox languageComboBox = null; @@ -109,8 +109,12 @@ public class EditRunPane extends JPanePlugin { private JLabel jLabel = null; + private JLabel scoreLabel = null; + private JTextField elapsedTimeTextField = null; + private JTextField scoreTextField = null; + private IFileViewer sourceViewer; private JCheckBox notifyTeamCheckBox = null; @@ -121,7 +125,7 @@ public class EditRunPane extends JPanePlugin { /** * This method initializes - * + * */ public EditRunPane() { super(); @@ -130,7 +134,7 @@ public EditRunPane() { /** * This method initializes this - * + * */ private void initialize() { this.setLayout(new BorderLayout()); @@ -142,35 +146,39 @@ private void initialize() { this.add(getGeneralPane(), java.awt.BorderLayout.CENTER); } + @Override public void setContestAndController(IInternalContest inContest, IInternalController inController) { super.setContestAndController(inContest, inController); log = getController().getLog(); addWindowCloserListener(); extractRuns = new ExtractRuns(inContest); } - + private void addWindowCloserListener() { SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { if (getParentFrame() != null) { getParentFrame().addWindowListener(new java.awt.event.WindowAdapter() { + @Override public void windowClosing(java.awt.event.WindowEvent e) { handleCancelButton(); } }); - } + } } }); } + @Override public String getPluginTitle() { return "Edit Run Pane"; } /** * This method initializes messagePane - * + * * @return javax.swing.JPanel */ private JPanel getMessagePane() { @@ -189,7 +197,7 @@ private JPanel getMessagePane() { /** * This method initializes buttonPane - * + * * @return javax.swing.JPanel */ private JPanel getButtonPane() { @@ -214,7 +222,7 @@ private Run getRunFromFields() { /** * This method initializes updateButton - * + * * @return javax.swing.JButton */ private JButton getUpdateButton() { @@ -224,6 +232,7 @@ private JButton getUpdateButton() { updateButton.setEnabled(false); updateButton.setMnemonic(java.awt.event.KeyEvent.VK_U); updateButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { updateRun(); } @@ -249,11 +258,13 @@ protected void updateRun() { Judgement judgement = (Judgement) getJudgementComboBox().getSelectedItem(); judgementRecord = new JudgementRecord(judgement.getElementId(), getContest().getClientId(), solved, false); + double score = getDoubleValue(getScoreTextField().getText()); + judgementRecord.setScore(score); judgementRecord.setSendToTeam(getNotifyTeamCheckBox().isSelected()); } newRun.setDeleted(deleteCheckBox.isSelected()); - + int elapsed = getIntegerValue(getElapsedTimeTextField().getText()); newRun.setElapsedMins(elapsed); @@ -271,7 +282,7 @@ protected void updateRun() { RunStates prevState = run.getStatus(); RunStates newRunState = (Run.RunStates) runStatusComboBox.getSelectedItem(); String errMsg = null; - + // Make sure it's safe to change the runstate switch(newRunState) { case JUDGED: @@ -283,7 +294,7 @@ protected void updateRun() { errMsg = "The run does not have any judgment records"; } break; - + case BEING_RE_JUDGED: // It is completely unreasonable to set a run's status to being re-judged errMsg = "You are not allowed to set the Run Status to BEING_RE_JUDGED"; @@ -297,7 +308,7 @@ protected void updateRun() { if(errMsg != null) { FrameUtilities.showMessage(this, "Can not set Run Status", errMsg); enableUpdateButton(); - return; + return; } int result = FrameUtilities.yesNoCancelDialog(this, "Are you sure you want to change status from " + // prevState.toString() + " to " + newRunState.toString() + "?", "Update/Change run status?"); @@ -314,19 +325,19 @@ protected void updateRun() { if (executable != null) { executionData = executable.getExecutionData(); } - + runResultFiles = new RunResultFiles(newRun, newRun.getProblemId(), judgementRecord, executionData); getController().updateRun(newRun, judgementRecord, runResultFiles); if (getParentFrame() != null) { getParentFrame().setVisible(false); } - + } /** * This method initializes cancelButton - * + * * @return javax.swing.JButton */ private JButton getCancelButton() { @@ -335,6 +346,7 @@ private JButton getCancelButton() { cancelButton.setText("Cancel"); cancelButton.setMnemonic(java.awt.event.KeyEvent.VK_C); cancelButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { handleCancelButton(); } @@ -382,6 +394,7 @@ public void setRun(final Run run) { FrameUtilities.waitCursor(this); SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { populateGUI(run); enableUpdateButtons(false); @@ -390,7 +403,7 @@ public void run() { } private void populateGUI(Run run2) { - + populatingGUI = true; if (run2 != null) { @@ -402,7 +415,13 @@ private void populateGUI(Run run2) { runInfoLabel.setText("Run " + run2.getNumber() + " (Site " + run2.getSiteNumber() + ") from " + teamName); deleteCheckBox.setSelected(run2.isDeleted()); elapsedTimeTextField.setText(new Long(run.getElapsedMins()).toString()); - + JudgementRecord judgementRecord = run2.getJudgementRecord(); + double score = 0; + if(judgementRecord != null) { + score = judgementRecord.getScore(); + } + getScoreTextField().setText(new Double(score).toString()); + getNotifyTeamCheckBox().setSelected(notifyTeam()); } else { @@ -411,11 +430,12 @@ private void populateGUI(Run run2) { runInfoLabel.setText("Could not get run"); deleteCheckBox.setSelected(false); elapsedTimeTextField.setText(""); + getScoreTextField().setText("0"); getNotifyTeamCheckBox().setSelected(false); } populateComboBoxes(); - + populatingGUI = false; } @@ -428,7 +448,7 @@ private void populateComboBoxes() { getProblemComboBox().removeAllItems(); getLanguageComboBox().removeAllItems(); getJudgementComboBox().removeAllItems(); - + runStatusComboBox.removeAllItems(); if (run == null) { @@ -482,7 +502,7 @@ private void populateComboBoxes() { log.info("Edit Run " + run.getNumber() + " judgement is " + judgementDescription); judgementLabel.setToolTipText(judgementDescription); } - + for (Judgement judgement : getContest().getJudgements()) { getJudgementComboBox().addItem(judgement); if (judgement.getElementId().equals(judgementId)) { @@ -490,15 +510,15 @@ private void populateComboBoxes() { } index++; } - + // Select judgement in combo box judgementComboBox.setSelectedIndex(selectedIndex); - + selectedIndex = -1; index = 0; runStatusComboBox.setSelectedIndex(selectedIndex); - + RunStates[] states = Run.RunStates.values(); for (RunStates runStates : states) { runStatusComboBox.addItem(runStates); @@ -507,7 +527,7 @@ private void populateComboBoxes() { } index++; } - + runStatusComboBox.setSelectedIndex(selectedIndex); } @@ -535,9 +555,17 @@ private int getIntegerValue(String s) { } } + private double getDoubleValue(String s) { + try { + return Double.parseDouble(s); + } catch (Exception e) { + return 0.0; + } + } + /** * Enable or disable Update button based on comparison of run to fields. - * + * */ public void enableUpdateButton() { @@ -560,11 +588,11 @@ public void enableUpdateButton() { enableButton |= (run.isDeleted() != getDeleteCheckBox().isSelected()); enableButton |= judgementChanged(); - + enableButton |= isStatusChanged(); - + enableButton |= notifyTeamChanged(); - + } getUpdateButton().setEnabled(enableButton); @@ -573,7 +601,7 @@ public void enableUpdateButton() { /** * return if status changed by user. - * + * * @return true if input run status different than combobox status */ private boolean isStatusChanged() { @@ -586,10 +614,13 @@ private boolean isStatusChanged() { private boolean judgementChanged() { if (run.isJudged()) { - + if (notifyTeamChanged()){ return true; } + if (scoreChanged()) { + return true; + } Judgement judgement = (Judgement) getJudgementComboBox().getSelectedItem(); if (judgement != null) { @@ -603,9 +634,20 @@ private boolean judgementChanged() { return false; } + private boolean scoreChanged() { + if (run.isJudged()) { + JudgementRecord judgementRecord = run.getJudgementRecord(); + if(judgementRecord != null) { + return(judgementRecord.getScore() != getDoubleValue(getScoreTextField().getText())); + } + } + + return false; + } + /** * For this run, send notification to team? - * @return + * @return */ private boolean notifyTeam(){ if (run.isJudged()) { @@ -618,7 +660,7 @@ private boolean notifyTeam(){ } return false; } - + /** * Has the notify team status changed ? * @return true if changed, false otherwise. @@ -629,7 +671,7 @@ private boolean notifyTeamChanged() { /** * This method initializes mainTabbedPane - * + * * @return javax.swing.JTabbedPane */ private JTabbedPane getMainTabbedPane() { @@ -641,7 +683,7 @@ private JTabbedPane getMainTabbedPane() { /** * This method initializes generalPane - * + * * @return javax.swing.JPanel */ private JPanel getGeneralPane() { @@ -650,6 +692,10 @@ private JPanel getGeneralPane() { jLabel.setBounds(new java.awt.Rectangle(73, 67, 142, 16)); jLabel.setText("Elapsed"); jLabel.setHorizontalAlignment(SwingConstants.RIGHT); + scoreLabel = new JLabel(); + scoreLabel.setBounds(new java.awt.Rectangle(300, 67, 65, 16)); + scoreLabel.setText("Score"); + scoreLabel.setHorizontalAlignment(javax.swing.SwingConstants.RIGHT); languageLabel = new JLabel(); languageLabel.setBounds(new java.awt.Rectangle(73, 166, 142, 16)); languageLabel.setText("Language"); @@ -684,12 +730,16 @@ private JPanel getGeneralPane() { generalPane.add(languageLabel, null); generalPane.add(jLabel, null); generalPane.add(getElapsedTimeTextField(), null); + generalPane.add(scoreLabel, null); + generalPane.add(getScoreTextField(), null); generalPane.add(getNotifyTeamCheckBox(), null); - + runStatusComboBox = new JComboBox(); runStatusComboBox.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { new Thread(new Runnable() { + @Override public void run() { enableUpdateButton(); } @@ -707,6 +757,7 @@ public void run() { public void showMessage(final String message) { SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { messageLabel.setText(message); } @@ -715,7 +766,7 @@ public void run() { /** * This method initializes executeButton - * + * * @return javax.swing.JButton */ private JButton getExecuteButton() { @@ -724,8 +775,10 @@ private JButton getExecuteButton() { executeButton.setText("Execute"); executeButton.setMnemonic(java.awt.event.KeyEvent.VK_X); executeButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { new Thread(new Runnable() { + @Override public void run() { executeRun(); } @@ -738,7 +791,7 @@ public void run() { /** * This method initializes viewSourceButton - * + * * @return javax.swing.JButton */ private JButton getViewSourceButton() { @@ -747,6 +800,7 @@ private JButton getViewSourceButton() { viewSourceButton.setText("View Source"); viewSourceButton.setMnemonic(java.awt.event.KeyEvent.VK_V); viewSourceButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { viewSourceFile(); } @@ -763,7 +817,7 @@ protected void viewSourceFile() { /** * This method initializes extractButton - * + * * @return javax.swing.JButton */ private JButton getExtractButton() { @@ -773,6 +827,7 @@ private JButton getExtractButton() { extractButton.setToolTipText("Extract Run contents"); extractButton.setMnemonic(java.awt.event.KeyEvent.VK_T); extractButton.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { extractRun(); } @@ -804,20 +859,20 @@ protected void extractRun() { protected void executeRun() { System.gc(); - + ExecuteTimerFrame executeFrame = new ExecuteTimerFrame(); - + executable = new Executable(getContest(), getController(), run, runFiles, executeFrame); IFileViewer fileViewer = executable.execute(); - + // Dump execution results files to log String executeDirctoryName = JudgementUtilities.getExecuteDirectoryName(getContest().getClientId()); Problem problem = getContest().getProblem(run.getProblemId()); ClientId clientId = getContest().getClientId(); List judgements = JudgementUtilities.getLastTestCaseJudgementList(getContest(), run); JudgementUtilities.dumpJudgementResultsToLog(log, clientId, run, executeDirctoryName, problem, judgements, executable.getExecutionData(), "", new Properties()); - + fileViewer.setVisible(true); } @@ -831,6 +886,7 @@ public void setRunAndFiles(Run run2, RunFiles runFiles2) { run = run2; runFiles = runFiles2; SwingUtilities.invokeLater(new Runnable() { + @Override public void run() { populateGUI(run); enableUpdateButtons(false); @@ -841,7 +897,7 @@ public void run() { /** * This method initializes judgementComboBox - * + * * @return javax.swing.JComboBox */ private JComboBox getJudgementComboBox() { @@ -850,6 +906,7 @@ private JComboBox getJudgementComboBox() { judgementComboBox.setLocation(new java.awt.Point(224, 97)); judgementComboBox.setSize(new java.awt.Dimension(263, 22)); judgementComboBox.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { enableUpdateButton(); } @@ -860,7 +917,7 @@ public void actionPerformed(java.awt.event.ActionEvent e) { /** * This method initializes deleteCheckBox - * + * * @return javax.swing.JCheckBox */ private JCheckBox getDeleteCheckBox() { @@ -869,6 +926,7 @@ private JCheckBox getDeleteCheckBox() { deleteCheckBox.setBounds(new java.awt.Rectangle(224, 196, 114, 21)); deleteCheckBox.setText("Delete Run"); deleteCheckBox.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { enableUpdateButton(); } @@ -879,7 +937,7 @@ public void actionPerformed(java.awt.event.ActionEvent e) { /** * This method initializes problemComboBox - * + * * @return javax.swing.JComboBox */ private JComboBox getProblemComboBox() { @@ -887,6 +945,7 @@ private JComboBox getProblemComboBox() { problemComboBox = new JComboBox(); problemComboBox.setBounds(new java.awt.Rectangle(224, 130, 263, 22)); problemComboBox.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { enableUpdateButton(); } @@ -897,7 +956,7 @@ public void actionPerformed(java.awt.event.ActionEvent e) { /** * This method initializes languageComboBox - * + * * @return javax.swing.JComboBox */ private JComboBox getLanguageComboBox() { @@ -905,6 +964,7 @@ private JComboBox getLanguageComboBox() { languageComboBox = new JComboBox(); languageComboBox.setBounds(new java.awt.Rectangle(224, 163, 263, 22)); languageComboBox.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { enableUpdateButton(); } @@ -915,7 +975,7 @@ public void actionPerformed(java.awt.event.ActionEvent e) { /** * This method initializes elapsedTimeTextField - * + * * @return javax.swing.JTextField */ private JTextField getElapsedTimeTextField() { @@ -926,6 +986,7 @@ private JTextField getElapsedTimeTextField() { elapsedTimeTextField.addKeyListener(new KeyAdapter() { // public void keyPressed(java.awt.event.KeyEvent e) { + @Override public void keyReleased(java.awt.event.KeyEvent e) { enableUpdateButton(); } @@ -934,6 +995,28 @@ public void keyReleased(java.awt.event.KeyEvent e) { return elapsedTimeTextField; } + /** + * This method initializes scoreTextField + * + * @return javax.swing.JTextField + */ + private JTextField getScoreTextField() { + if (scoreTextField == null) { + scoreTextField = new JTextField(); + scoreTextField.setBounds(new java.awt.Rectangle(380, 65, 65, 21)); + scoreTextField.setDocument(new DoubleDocument()); + + scoreTextField.addKeyListener(new KeyAdapter() { + // public void keyPressed(java.awt.event.KeyEvent e) { + @Override + public void keyReleased(java.awt.event.KeyEvent e) { + enableUpdateButton(); + } + }); + } + return scoreTextField; + } + private void createAndViewFile(SerializedFile file, String title) { // TODO the executeable dir name should be from the model, eh ? Executable tempEexecutable = new Executable(getContest(), getController(), run, runFiles, null); @@ -963,7 +1046,7 @@ private void createAndViewFile(SerializedFile file, String title) { /** * This method initializes notifyTeamCheckBox - * + * * @return javax.swing.JCheckBox */ private JCheckBox getNotifyTeamCheckBox() { @@ -973,6 +1056,7 @@ private JCheckBox getNotifyTeamCheckBox() { notifyTeamCheckBox.setSelected(false); notifyTeamCheckBox.setText("Notify Team"); notifyTeamCheckBox.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent e) { enableUpdateButton(); } From e0c2b7cd0b6a87b6558e0372cbd3aacfbdfdcd9f Mon Sep 17 00:00:00 2001 From: John Buck Date: Mon, 28 Jul 2025 17:28:37 -0400 Subject: [PATCH 02/14] i_1006 use correct test data group for max_score We can't just use the first test data group and assume it's the root. We have to walk up the getParent() chain until we find the null parent. At that point, we know we're at the top of the tree, and we can use that getRangeMax(). As per Tim deBoer, he suggested we do not send Double.POSITIVE_INFINITY (infinity) if the max is in fact infinity. Rather, just leave the property out of the record. This contradicts the current 2023-06 specification that requires the max_score property. --- .../ecs/pc2/clics/API202306/CLICSProblem.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblem.java b/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblem.java index bf9602fe2..fab971907 100644 --- a/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblem.java +++ b/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblem.java @@ -98,9 +98,23 @@ public CLICSProblem(IInternalContest model, Problem problem, int ordinal) { ProblemDataFiles problemDataFiles = model.getProblemDataFile(problem); if(problemDataFiles != null) { TestDataGroup [] testDataGroups = problemDataFiles.getJudgesDataGroups(); - // Really, there's only 1 top level TestDataGruop + // Really, there's only 1 top (root) level TestDataGroup which we have to find. Start at the + // first group and walk up the tree to the root (parent being null) if(testDataGroups != null && testDataGroups.length > 0) { - max_score = testDataGroups[0].getRangeMax(); + TestDataGroup tdg = testDataGroups[0]; + // Paranoia: this had better not be null. + if(tdg != null) { + TestDataGroup parentTdg = tdg.getParent(); + while(parentTdg != null) { + tdg = parentTdg; + parentTdg = tdg.getParent(); + } + // Departure from spec: if upper range is infinity, just leave it out (max_score will be null). + // Primarily for the Resolver as it doesn't want to see "infinity" as a value. + if(tdg.getRangeMax() != Double.POSITIVE_INFINITY) { + max_score = tdg.getRangeMax(); + } + } } } } From 2d7ebfeb53e59b5550724f904bb0bf5331aac9c3 Mon Sep 17 00:00:00 2001 From: John Buck Date: Mon, 28 Jul 2025 22:00:37 -0400 Subject: [PATCH 03/14] i_1006 Fix bug with no submission id override The value -1 is legal for a submission override if the id is, in fact, 1. 0 is the only illegal value. Not sure why I had it as -1 originally. --- src/edu/csus/ecs/pc2/clics/API202306/SubmissionService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/edu/csus/ecs/pc2/clics/API202306/SubmissionService.java b/src/edu/csus/ecs/pc2/clics/API202306/SubmissionService.java index d07b64f29..a7af6b347 100644 --- a/src/edu/csus/ecs/pc2/clics/API202306/SubmissionService.java +++ b/src/edu/csus/ecs/pc2/clics/API202306/SubmissionService.java @@ -485,9 +485,9 @@ public synchronized Response addNewSubmission(@Context HttpServletRequest servle return Response.status(Status.BAD_REQUEST).entity("invalid json supplied").build(); } - // These next three are for admin users only + // These next two are for admin users only long overrideTimeMS = -1; - long overrideSubmissionID = -1; + long overrideSubmissionID = 0; Log log = controller.getLog(); String user = sc.getUserPrincipal().getName(); @@ -570,7 +570,7 @@ public synchronized Response addNewSubmission(@Context HttpServletRequest servle } overrideSubmissionID = Utilities.stringToLong(sub.getId()); if(overrideSubmissionID < 0) { - overrideSubmissionID = -1; + overrideSubmissionID = 0; } } From 0be1e94c6e5d3d5274d043894d7920da0bc04a12 Mon Sep 17 00:00:00 2001 From: John Buck Date: Mon, 28 Jul 2025 22:00:37 -0400 Subject: [PATCH 04/14] i_1006 Fix bug with no submission id override The value -1 is legal for a submission override if the id is, in fact, 1. 0 is the only illegal value. Not sure why I had it as -1 originally. From 315e2cba64e504caab569838336bb26144ba9383 Mon Sep 17 00:00:00 2001 From: John Buck Date: Mon, 28 Jul 2025 17:28:37 -0400 Subject: [PATCH 05/14] i_1006 use correct test data group for max_score We can't just use the first test data group and assume it's the root. We have to walk up the getParent() chain until we find the null parent. At that point, we know we're at the top of the tree, and we can use that getRangeMax(). As per Tim deBoer, he suggested we do not send Double.POSITIVE_INFINITY (infinity) if the max is in fact infinity. Rather, just leave the property out of the record. This contradicts the current 2023-06 specification that requires the max_score property. From a47b093d4d7be3ba22203aea039b9393286aa8ab Mon Sep 17 00:00:00 2001 From: John Buck Date: Thu, 24 Jul 2025 12:20:44 -0400 Subject: [PATCH 06/14] i_1006 changes to support results.tsv/csv for point scoring Updated NSA to score correctly for point scoring since the ResultsFile class uses NSA to generate results.tsv and results.csv. --- ...StandingsPointScoringRecordComparator.java | 110 ++++++++++++++ .../pc2/core/scoring/NewScoringAlgorithm.java | 142 +++++++++++++----- .../pc2/core/scoring/ProblemScoreRecord.java | 26 +++- .../csus/ecs/pc2/exports/ccs/ResultsFile.java | 109 +++++++++----- 4 files changed, 309 insertions(+), 78 deletions(-) create mode 100644 src/edu/csus/ecs/pc2/core/scoring/FinalsStandingsPointScoringRecordComparator.java diff --git a/src/edu/csus/ecs/pc2/core/scoring/FinalsStandingsPointScoringRecordComparator.java b/src/edu/csus/ecs/pc2/core/scoring/FinalsStandingsPointScoringRecordComparator.java new file mode 100644 index 000000000..52d06ba9c --- /dev/null +++ b/src/edu/csus/ecs/pc2/core/scoring/FinalsStandingsPointScoringRecordComparator.java @@ -0,0 +1,110 @@ +// Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. +package edu.csus.ecs.pc2.core.scoring; + +import java.io.Serializable; +import java.util.Comparator; + +import edu.csus.ecs.pc2.core.list.AccountList; +import edu.csus.ecs.pc2.core.list.AccountNameCaseComparator; +import edu.csus.ecs.pc2.core.model.Account; + +/** + * Sorts StandingsRecord according to the ACM-ICPC World Finals Rules (as of 2025) for point scoring contests + * + * @author John Buck + * @version $Id$ + */ + +// $HeadURL$ +public class FinalsStandingsPointScoringRecordComparator implements Serializable, Comparator { + + /** + * + */ + private static final long serialVersionUID = 1L; + + private AccountNameCaseComparator accountNameCaseComparator = new AccountNameCaseComparator(); + + private AccountList cachedAccountList; + + /** + * Compares its two arguments for order. Returns a negative integer, zero, or a positive integer as the first argument is less + * than, equal to, or greater than the second. + *

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

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

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

+ * + * It is generally the case, but not strictly required that (compare(x, y)==0) == (x.equals(y)). Generally + * speaking, any comparator that violates this condition should clearly indicate this fact. The recommended language is "Note: + * this comparator imposes orderings that are inconsistent with equals." + * + * @return a negative integer, zero, or a positive integer as the first argument is less than, equal to, or greater than the + * second. + * @throws ClassCastException + * if the arguments' types prevent them from being compared by this Comparator. + */ + @Override + public int compare(StandingsRecord o1, StandingsRecord o2) { + int status = 0; + double aScore, bScore; + long aTime, bTime; + int aHash, bHash; + String aName, bName; + + StandingsRecord teamA = o1; + StandingsRecord teamB = o2; + aScore = teamA.getScore(); + aTime = teamA.getLastSolved(); + Account accountA = cachedAccountList.getAccount(teamA.getClientId()); + aName = accountA.getDisplayName(); + aHash = teamA.getClientId().hashCode(); + bScore = teamB.getScore(); + bTime = teamB.getLastSolved(); + Account accountB = cachedAccountList.getAccount(teamB.getClientId()); + bName = accountB.getDisplayName(); + bHash = teamB.getClientId().hashCode(); + + // + // Primary Sort = score (high to low) + // Secondary Sort = time (low to high) + // Third Sort = teamName (low to high) + // Fourth Sort = clientId (low to high) + + int nameComparison = accountNameCaseComparator.compare(aName, bName); + if ((bScore == aScore) && (bTime == aTime) && (nameComparison == 0) + && (bHash == aHash)) { + status = 0; // elements equal, this shouldn't happen, Tammy... + } else { + if ((bScore > aScore) + || ((bScore == aScore) && (bTime < aTime)) + || ((bScore == aScore) && (bTime == aTime) && (nameComparison > 0)) + || ((bScore == aScore) && (bTime == aTime) + && (nameComparison == 0) && (bHash < aHash))) { + status = 1; // a considered greater then b + } else { + status = -1; // a considered less then b + } + } + return status; + } + + /** + * @param accountList + * The cachedAccountList to set. + */ + public void setCachedAccountList(AccountList accountList) { + this.cachedAccountList = accountList; + } +} diff --git a/src/edu/csus/ecs/pc2/core/scoring/NewScoringAlgorithm.java b/src/edu/csus/ecs/pc2/core/scoring/NewScoringAlgorithm.java index 8b28b6bc5..d73413288 100644 --- a/src/edu/csus/ecs/pc2/core/scoring/NewScoringAlgorithm.java +++ b/src/edu/csus/ecs/pc2/core/scoring/NewScoringAlgorithm.java @@ -2,8 +2,10 @@ package edu.csus.ecs.pc2.core.scoring; import java.io.IOException; +import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Comparator; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -29,6 +31,7 @@ import edu.csus.ecs.pc2.core.model.Group; import edu.csus.ecs.pc2.core.model.IInternalContest; import edu.csus.ecs.pc2.core.model.Judgement; +import edu.csus.ecs.pc2.core.model.JudgementRecord; import edu.csus.ecs.pc2.core.model.Problem; import edu.csus.ecs.pc2.core.model.Run; import edu.csus.ecs.pc2.core.model.RunUtilities; @@ -61,10 +64,10 @@ public class NewScoringAlgorithm extends Plugin implements INewScoringAlgorithm private boolean respectEOC = false; - private DefaultStandingsRecordComparator comparator = new DefaultStandingsRecordComparator(); - private PermissionList permissionList = new PermissionList(); + private boolean isPointScoring = false; + /** * Return a list of regional winners. * @@ -182,6 +185,7 @@ public StandingsRecord[] getStandingsRecords(IInternalContest contest, Integer d } setContest(contest); + isPointScoring = contest.getContestInformation().isScoreboardTypeScore(); /* * Get all the teams, then create a new vector of only those teams shown on the @@ -213,7 +217,22 @@ public StandingsRecord[] getStandingsRecords(IInternalContest contest, Integer d for (Account account : accounts) { accountList.add(account); } - comparator.setCachedAccountList(accountList); + Comparator comparator; + + // Note: each of DefaultStandingsRecordComparator and DefaultPointScoringStandingsRecordComparator + // implements the java.util.Comparator interface. However, we need additional information in the + // comparator for StandingsRecord, namely, the accountList (for looking up names). This is why we + // instantiate each object separately, set the accountlist then assign to src. I suppose we could create + // another interface that extends java.util.Comparator without our method to set the cached account list. + if(!isPointScoring) { + DefaultStandingsRecordComparator srcPassFailRecordComparator = new DefaultStandingsRecordComparator(); + srcPassFailRecordComparator.setCachedAccountList(accountList); + comparator = srcPassFailRecordComparator; + } else { + DefaultPointScoringStandingsRecordComparator srcPointScoringRecordComparator = new DefaultPointScoringStandingsRecordComparator(); + srcPointScoringRecordComparator.setCachedAccountList(accountList); + comparator = srcPointScoringRecordComparator; + } if (runs == null) { runs = ScoreboardUtilities.getGroupFilteredRuns(getContest(), wantedGroups); @@ -357,14 +376,23 @@ public String getStandings(IInternalContest contest, Run[] runs, Integer divisio */ boolean isTied(StandingsRecord standingsRecord, StandingsRecord standingsRecord2) { - if (standingsRecord2.getNumberSolved() != standingsRecord.getNumberSolved()) { - return false; - } - if (standingsRecord2.getPenaltyPoints() != standingsRecord.getPenaltyPoints()) { - return false; - } - if (standingsRecord2.getLastSolved() != standingsRecord.getLastSolved()) { - return false; + if(isPointScoring) { + if(standingsRecord.getScore() != standingsRecord2.getScore()) { + return false; + } + if(standingsRecord.getLastSolved() != standingsRecord2.getLastSolved()) { + return false; + } + } else { + if (standingsRecord2.getNumberSolved() != standingsRecord.getNumberSolved()) { + return false; + } + if (standingsRecord2.getPenaltyPoints() != standingsRecord.getPenaltyPoints()) { + return false; + } + if (standingsRecord2.getLastSolved() != standingsRecord.getLastSolved()) { + return false; + } } return true; } @@ -482,15 +510,18 @@ private GrandTotals addProblemSummaryMememento(IMemento summaryMememento, Standi ProblemSummaryInfo problemSummaryInfo = summaryRow.get(i + 1); if (problemSummaryInfo != null) { - long solveTime = problemSummaryInfo.getSolutionTime(); - if (fastestSolved[i] == 0 || solveTime < fastestSolved[i]) { - fastestSolved[i] = solveTime; - } - if (solveTime > lastSolutionTime[i]) { - lastSolutionTime[i] = solveTime; - } - if (problemSummaryInfo.isSolved()) { - numberSolved[i]++; + // Check if solved, even somewhat solved for PS + if(!isPointScoring || problemSummaryInfo.getScore() == 0.0) { + long solveTime = problemSummaryInfo.getSolutionTime(); + if (fastestSolved[i] == 0 || solveTime < fastestSolved[i]) { + fastestSolved[i] = solveTime; + } + if (solveTime > lastSolutionTime[i]) { + lastSolutionTime[i] = solveTime; + } + if (problemSummaryInfo.isSolved()) { + numberSolved[i]++; + } } numberAttempts[i] += problemSummaryInfo.getNumberSubmitted(); } @@ -503,6 +534,8 @@ private GrandTotals addProblemSummaryMememento(IMemento summaryMememento, Standi IMemento problemMemento = summaryMememento.createChild("problem"); problemMemento.putInteger("id", id); problemMemento.putString("title", problems[i].getDisplayName()); + problemMemento.putString("color", problems[i].getColorName()); + problemMemento.putString("rgb", problems[i].getColorRGB()); problemMemento.putLong("attempts", numberAttempts[i]); grandTotals.incrementTotalAttempts(numberAttempts[i]); @@ -533,7 +566,8 @@ private IMemento addTeamMemento(IMemento mementoRoot, IInternalContest contest, IMemento standingsRecordMemento = mementoRoot.createChild("teamStanding"); - String teamVarDisplayString = contest.getContestInformation().getTeamScoreboardDisplayFormat(); + ContestInformation contestInformation = contest.getContestInformation(); + String teamVarDisplayString = contestInformation.getTeamScoreboardDisplayFormat(); Account account = contest.getAccount(standingsRecord.getClientId()); HashSet groups = account.getGroupIds(); @@ -541,7 +575,12 @@ private IMemento addTeamMemento(IMemento mementoRoot, IInternalContest contest, standingsRecordMemento.putLong("firstSolved", standingsRecord.getFirstSolved()); standingsRecordMemento.putLong("lastSolved", standingsRecord.getLastSolved()); - standingsRecordMemento.putLong("points", standingsRecord.getPenaltyPoints()); + if(contestInformation.isScoreboardTypeScore()) { + DecimalFormat df = new DecimalFormat("0.0###"); + standingsRecordMemento.putString("score", df.format(standingsRecord.getScore())); + } else { + standingsRecordMemento.putLong("points", standingsRecord.getPenaltyPoints()); + } standingsRecordMemento.putInteger("solved", standingsRecord.getNumberSolved()); standingsRecordMemento.putInteger("rank", standingsRecord.getRankNumber()); standingsRecordMemento.putInteger("index", indexNumber); @@ -633,7 +672,11 @@ private StandingsRecord[] computeStandingStandingsRecords(Run[] runs, Account[] // old ProblemScoreRecord problemScoreRecord = new ProblemScoreRecord(teamProblemRuns, problem, properties); ProblemScoreRecord problemScoreRecord = createProblemScoreRecord(teamProblemRuns, problem, properties); - standingsRecord.setPenaltyPoints(standingsRecord.getPenaltyPoints() + problemScoreRecord.getPoints()); + if(isPointScoring) { + standingsRecord.setScore(standingsRecord.getScore() + problemScoreRecord.getScore()); + } else { + standingsRecord.setPenaltyPoints(standingsRecord.getPenaltyPoints() + problemScoreRecord.getPoints()); + } if (problemScoreRecord.getSolutionTime() > standingsRecord.getLastSolved()) { standingsRecord.setLastSolved(problemScoreRecord.getSolutionTime()); @@ -659,10 +702,12 @@ private StandingsRecord[] computeStandingStandingsRecords(Run[] runs, Account[] } problemNumber++; } - long penaltyPoints = standingsRecord.getPenaltyPoints(); - int scoreAdjustment = account.getScoringAdjustment(); - if (penaltyPoints > 0 && scoreAdjustment != 0) { - standingsRecord.setPenaltyPoints(Math.max(penaltyPoints+scoreAdjustment,0)); + if(!isPointScoring) { + long penaltyPoints = standingsRecord.getPenaltyPoints(); + int scoreAdjustment = account.getScoringAdjustment(); + if (penaltyPoints > 0 && scoreAdjustment != 0) { + standingsRecord.setPenaltyPoints(Math.max(penaltyPoints+scoreAdjustment,0)); + } } standingsRecords[standRecCount] = standingsRecord; standRecCount++; @@ -719,6 +764,8 @@ public ProblemScoreRecord createProblemScoreRecord(Run[] runs, Problem problem, int numberJudged = 0; + double score = 0; + Arrays.sort(runs, new RunCompartorByElapsed()); for (Run run : runs) { @@ -736,11 +783,27 @@ public ProblemScoreRecord createProblemScoreRecord(Run[] runs, Problem problem, numberPending++; } - if (run.isSolved() && solutionTime == 0) { - // set to solved, set solution time - solved = true; - solutionTime = run.getElapsedMins(); - solvingRun = run; + if (run.isSolved()) { + if(isPointScoring) { + // Point scoring works somewhat differently, in that there may be more than one + // accepted solution. We look for the one with the biggest score. + JudgementRecord jr = run.getJudgementRecord(); + if(jr != null) { + double dScore; + dScore = jr.getScore(); + if(dScore > score) { + score = dScore; + solutionTime = run.getElapsedMins(); + solvingRun = run; + solved = true; + } + } + } else if(solutionTime == 0) { + // set to solved, set solution time + solved = true; + solutionTime = run.getElapsedMins(); + solvingRun = run; + } } if (run.isJudged() && (!solved)) { @@ -767,8 +830,11 @@ public ProblemScoreRecord createProblemScoreRecord(Run[] runs, Problem problem, (securityViolationBeforeYes * getSVPenalty(properties)); } - return new ProblemScoreRecord(solved, solvingRun, problem, points, solutionTime, numberSubmissions, submissionsBeforeYes, numberPending, numberJudged); - + ProblemScoreRecord psr = new ProblemScoreRecord(solved, solvingRun, problem, points, solutionTime, numberSubmissions, submissionsBeforeYes, numberPending, numberJudged); + if(isPointScoring) { + psr.setScore(score); + } + return(psr); } private int getCEPenalty(Properties properties) { @@ -794,6 +860,7 @@ private IMemento addProblemSummaryRow(IMemento mementoRoot, int index, ProblemSu summaryInfoMemento.putString("shortName", summaryInfo.getShortName()); summaryInfoMemento.putInteger("attempts", summaryInfo.getNumberSubmitted()); summaryInfoMemento.putInteger("points", summaryInfo.getPenaltyPoints()); + summaryInfoMemento.putDouble("score", summaryInfo.getScore()); summaryInfoMemento.putLong("solutionTime", summaryInfo.getSolutionTime()); summaryInfoMemento.putBoolean("isSolved", summaryInfo.isSolved()); summaryInfoMemento.putBoolean("isPending", summaryInfo.isUnJudgedRuns()); @@ -806,7 +873,11 @@ private ProblemSummaryInfo createProblemSummaryInfo(Run[] runs, Problem problem, summaryInfo.setNumberSubmitted(problemScoreRecord.getNumberSubmissions()); summaryInfo.setJudgedRunCount(problemScoreRecord.getNumberJudgedSubmissions()); summaryInfo.setPendingRunCount(problemScoreRecord.getNumberPendingSubmissions()); - summaryInfo.setPenaltyPoints((int) problemScoreRecord.getPoints()); + if(isPointScoring) { + summaryInfo.setScore(problemScoreRecord.getScore()); + } else { + summaryInfo.setPenaltyPoints((int) problemScoreRecord.getPoints()); + } summaryInfo.setSolutionTime(problemScoreRecord.getSolutionTime()); summaryInfo.setUnJudgedRuns(false); summaryInfo.setSolved(problemScoreRecord.isSolved()); @@ -978,6 +1049,7 @@ private IMemento createSummaryMomento(ContestInformation contestInformation, XML memento.putString("systemVersion", versionInfo.getVersionNumber() + " build " + versionInfo.getBuildNumber()); memento.putString("systemURL", versionInfo.getSystemURL()); memento.putString("currentDate", new Date().toString()); + memento.putString("scoreType", contestInformation.getScoreboardType().toString().toLowerCase()); memento.putString("generatorId", "$Id$"); return memento; diff --git a/src/edu/csus/ecs/pc2/core/scoring/ProblemScoreRecord.java b/src/edu/csus/ecs/pc2/core/scoring/ProblemScoreRecord.java index c04af3c16..07b9dd715 100644 --- a/src/edu/csus/ecs/pc2/core/scoring/ProblemScoreRecord.java +++ b/src/edu/csus/ecs/pc2/core/scoring/ProblemScoreRecord.java @@ -8,7 +8,7 @@ /** * Holds summary score information for a single Problem. - * + * * @author pc2@ecs.csus.edu * @version $Id: ProblemScoreRecord.java 181 2011-04-11 03:21:46Z laned $ */ @@ -34,8 +34,10 @@ public class ProblemScoreRecord { private int submissionsBeforeYes; + private double score = 0; + /** - * + * * @param solved * @param solvingRun * @param problem @@ -62,7 +64,7 @@ public ProblemScoreRecord(boolean solved, Run solvingRun, Problem problem, long /** * Constructor deprecated. - * + * * This class is now used to store values, before it was used to both calculate and store values.
* See {@link edu.csus.ecs.pc2.core.scoring.NewScoringAlgorithm#createProblemScoreRecord(Run[], Problem, Properties)} as an example of how to compute values. */ @@ -73,7 +75,7 @@ public ProblemScoreRecord(Run[] teamProblemRuns, Problem problem2, Properties pr /** * Has problem been solved?. - * + * * @return true if problem solved. */ public boolean isSolved() { @@ -86,7 +88,7 @@ public long getSolutionTime() { /** * All non-deleted Runs. - * + * * @return */ public int getNumberSubmissions() { @@ -95,7 +97,7 @@ public int getNumberSubmissions() { /** * Time/Penalty points for this problem. - * + * * @return number of points */ public long getPoints() { @@ -104,7 +106,7 @@ public long getPoints() { /** * First run which solved this Problem. - * + * * @return null if run solved the problem. */ public Run getSolvingRun() { @@ -113,7 +115,7 @@ public Run getSolvingRun() { /** * Number of submissions before first yes. - * + * * @return */ public int getSubmissionsBeforeYes() { @@ -141,4 +143,12 @@ public int getNumberPendingSubmissions() { return numberPendingSubmissions; } + public double getScore() { + return score; + } + + public void setScore(double score) { + this.score = score; + } + } diff --git a/src/edu/csus/ecs/pc2/exports/ccs/ResultsFile.java b/src/edu/csus/ecs/pc2/exports/ccs/ResultsFile.java index 222dbe6d6..1afd52210 100644 --- a/src/edu/csus/ecs/pc2/exports/ccs/ResultsFile.java +++ b/src/edu/csus/ecs/pc2/exports/ccs/ResultsFile.java @@ -1,6 +1,7 @@ // Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. package edu.csus.ecs.pc2.exports.ccs; +import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -17,6 +18,7 @@ import edu.csus.ecs.pc2.core.model.IInternalContest; import edu.csus.ecs.pc2.core.scoring.CitationRankInformation; import edu.csus.ecs.pc2.core.scoring.DefaultScoringAlgorithm; +import edu.csus.ecs.pc2.core.scoring.FinalsStandingsPointScoringRecordComparator; import edu.csus.ecs.pc2.core.scoring.FinalsStandingsRecordComparator; import edu.csus.ecs.pc2.core.scoring.NewScoringAlgorithm; import edu.csus.ecs.pc2.core.scoring.StandingsRecord; @@ -56,8 +58,6 @@ public class ResultsFile { private FinalizeData finalizeData = null; - private FinalsStandingsRecordComparator comparator; - public void setFinalizeData(FinalizeData finalizeData) { this.finalizeData = finalizeData; } @@ -134,6 +134,9 @@ private int getMedian(StandingsRecord[] srArray) { public String[] createFileLines(IInternalContest contest, Group group, String resultFileTitleFieldName, boolean isTSV) { Vector lines = new Vector(); + boolean isPointScoring = contest.getContestInformation().isScoreboardTypeScore(); + + DecimalFormat dblFormatter = null; finalizeData = contest.getFinalizeData(); @@ -149,7 +152,11 @@ public String[] createFileLines(IInternalContest contest, Group group, String re if (isTSV) { lines.addElement(resultFileTitleFieldName + TAB + "1"); } else { - lines.addElement("teamId,rank,medalCitation,problemsSolved,totalTime,lastProblemTime,siteCitation,citation"); + if(isPointScoring) { + lines.addElement("teamId,rank,medalCitation,problemsSolved,score,time,siteCitation,citation"); + } else { + lines.addElement("teamId,rank,medalCitation,problemsSolved,totalTime,lastProblemTime,siteCitation,citation"); + } } // return ranked teams @@ -197,12 +204,19 @@ public String[] createFileLines(IInternalContest contest, Group group, String re for (Account account : accounts) { accountList.add(account); } - comparator = new FinalsStandingsRecordComparator(); - comparator.setCachedAccountList(accountList); - comparator.setLastRank(lastMedalRank); - comparator.setMedian(median); - comparator.setUseWFGroupRanking(finalizeData.isUseWFGroupRanking()); - Arrays.sort(standingsRecords, comparator); + if(isPointScoring) { + FinalsStandingsPointScoringRecordComparator comparator = new FinalsStandingsPointScoringRecordComparator(); + comparator.setCachedAccountList(accountList); + Arrays.sort(standingsRecords, comparator); + dblFormatter = new DecimalFormat("0.00##"); + } else { + FinalsStandingsRecordComparator comparator = new FinalsStandingsRecordComparator(); + comparator.setCachedAccountList(accountList); + comparator.setLastRank(lastMedalRank); + comparator.setMedian(median); + comparator.setUseWFGroupRanking(finalizeData.isUseWFGroupRanking()); + Arrays.sort(standingsRecords, comparator); + } int realRank = 0; if (highestHonorSolvedCount == 0) { @@ -231,15 +245,21 @@ public String[] createFileLines(IInternalContest contest, Group group, String re boolean isHighHonor = false; boolean isHonor = false; - if (finalizeData.isUseWFGroupRanking()) { - if (record.getNumberSolved() >= highestHonorSolvedCount) { - isHighestHonor = true; - } else if (record.getNumberSolved() >= highHonorSolvedCount) { - isHighHonor = true; + // Sorry, no "Bill" rules for point scoring contests as it doesnt make sense the way its defined. + if(!isPointScoring) { + if (finalizeData.isUseWFGroupRanking()) { + if (record.getNumberSolved() >= highestHonorSolvedCount) { + isHighestHonor = true; + } else if (record.getNumberSolved() >= highHonorSolvedCount) { + isHighHonor = true; + } else if (record.getNumberSolved() >= median) { + isHonor = true; + } } else if (record.getNumberSolved() >= median) { isHonor = true; } - } else if (record.getNumberSolved() >= median) { + } else { + // This will force "RANKED" in a kludgy way isHonor = true; } @@ -250,7 +270,7 @@ public String[] createFileLines(IInternalContest contest, Group group, String re String rank = ""; if (!HONORABLE.equalsIgnoreCase(award)) { - if (finalizeData.isUseWFGroupRanking() && realRank > lastMedalRank) { + if (!isPointScoring && finalizeData.isUseWFGroupRanking() && realRank > lastMedalRank) { if (record.getNumberSolved() != lastSolvedNum) { lastSolvedNum = record.getNumberSolved(); rankNumber = realRank; @@ -260,23 +280,33 @@ public String[] createFileLines(IInternalContest contest, Group group, String re rank = Integer.toString(record.getRankNumber()); } + String awardLine; if (isTSV) { - lines.addElement(reservationId + TAB // + awardLine = reservationId + TAB // + rank + TAB // + award + TAB // - + record.getNumberSolved() + TAB // - + record.getPenaltyPoints() + TAB // - + record.getLastSolved()); + + record.getNumberSolved() + TAB; + if(isPointScoring) { + awardLine += dblFormatter.format(record.getScore()); + } else { + awardLine += record.getPenaltyPoints(); + } + awardLine += TAB + record.getLastSolved(); } else { - // teamId,rank,medalCitation,problemsSolved,totalTime,lastProblemTime,siteCitation,citation - lines.addElement(reservationId + COMMA // + // teamId,rank,medalCitation,problemsSolved,totalTime|score,lastProblemTime,siteCitation,citation + awardLine = reservationId + COMMA // + rank + COMMA // + award + COMMA // - + record.getNumberSolved() + COMMA // - + record.getPenaltyPoints() + COMMA // - + record.getLastSolved() + COMMA // then siteCitation - + COMMA); // then citation + + record.getNumberSolved() + COMMA; + if(isPointScoring) { + awardLine += dblFormatter.format(record.getScore()); + } else { + awardLine += record.getPenaltyPoints(); + } + awardLine += COMMA + record.getLastSolved() + COMMA // then siteCitation + + COMMA; // then citation } + lines.addElement(awardLine); } return lines.toArray(new String[lines.size()]); @@ -297,6 +327,8 @@ public CitationRankInformation createCitationRankInformation(IInternalContest co int highestHonorSolvedCount = 0; int highHonorSolvedCount = 0; CitationRankInformation ri = new CitationRankInformation(); + boolean isPointScoring = contest.getContestInformation().isScoreboardTypeScore(); + DecimalFormat dblFormatter; finalizeData = contest.getFinalizeData(); if (finalizeData == null) { @@ -320,7 +352,7 @@ public CitationRankInformation createCitationRankInformation(IInternalContest co return(ri); } - if (finalizeData.isUseWFGroupRanking() && finalizeData.isCustomizeHonorsSolvedCount()) { + if (!isPointScoring && finalizeData.isUseWFGroupRanking() && finalizeData.isCustomizeHonorsSolvedCount()) { if (finalizeData.getHighestHonorSolvedCount() != 0) { highestHonorSolvedCount = finalizeData.getHighestHonorSolvedCount(); } @@ -339,12 +371,19 @@ public CitationRankInformation createCitationRankInformation(IInternalContest co for (Account account : accounts) { accountList.add(account); } - comparator = new FinalsStandingsRecordComparator(); - comparator.setCachedAccountList(accountList); - comparator.setLastRank(lastMedalRank); - comparator.setMedian(median); - comparator.setUseWFGroupRanking(finalizeData.isUseWFGroupRanking()); - Arrays.sort(standingsRecords, comparator); + if(isPointScoring) { + FinalsStandingsPointScoringRecordComparator comparator = new FinalsStandingsPointScoringRecordComparator(); + comparator.setCachedAccountList(accountList); + Arrays.sort(standingsRecords, comparator); + dblFormatter = new DecimalFormat("0.00##"); + } else { + FinalsStandingsRecordComparator comparator = new FinalsStandingsRecordComparator(); + comparator.setCachedAccountList(accountList); + comparator.setLastRank(lastMedalRank); + comparator.setMedian(median); + comparator.setUseWFGroupRanking(finalizeData.isUseWFGroupRanking()); + Arrays.sort(standingsRecords, comparator); + } int rank; if (highestHonorSolvedCount == 0) { @@ -360,7 +399,7 @@ public CitationRankInformation createCitationRankInformation(IInternalContest co boolean isHighHonor = false; boolean isHonor = false; - if (finalizeData.isUseWFGroupRanking()) { + if (!isPointScoring && finalizeData.isUseWFGroupRanking()) { if (record.getNumberSolved() >= highestHonorSolvedCount) { isHighestHonor = true; } else if (record.getNumberSolved() >= highHonorSolvedCount) { @@ -377,7 +416,7 @@ public CitationRankInformation createCitationRankInformation(IInternalContest co rank = record.getRankNumber(); if (record.getNumberSolved() > 0 && !HONORABLE.equalsIgnoreCase(getMedalCitation(rank, finalizeData, isHighestHonor, isHighHonor, isHonor))) { - if (finalizeData.isUseWFGroupRanking()) { + if (!isPointScoring && finalizeData.isUseWFGroupRanking()) { if(isHighestHonor) { ri.updateLastHighestHonorsRank(rank); } else if(isHighHonor) { From 6e907d4fc447647009490e02b6536fec975f2f52 Mon Sep 17 00:00:00 2001 From: John Buck Date: Sat, 26 Jul 2025 19:48:27 -0400 Subject: [PATCH 07/14] i_1149 Allow empty source code submissions on API EP Previously, if a submission received on the CLICS API contained an empty source code (main) file, the submission was rejected. This commit allows empty source to be submitted, which should ultimately cause a Compile Error. --- .../csus/ecs/pc2/clics/API202306/SubmissionService.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/edu/csus/ecs/pc2/clics/API202306/SubmissionService.java b/src/edu/csus/ecs/pc2/clics/API202306/SubmissionService.java index a7af6b347..a3e4596ca 100644 --- a/src/edu/csus/ecs/pc2/clics/API202306/SubmissionService.java +++ b/src/edu/csus/ecs/pc2/clics/API202306/SubmissionService.java @@ -606,9 +606,15 @@ public synchronized Response addNewSubmission(@Context HttpServletRequest servle if("".equals(fileName)) { return Response.status(Response.Status.BAD_REQUEST).entity("no file name specified").build(); } + // allow contestant submission of a zero length file. This will generate a CE (hopefully). + // if the following code is uncommented, the submission is not made and a 400 is returned to the submitter. + // it appears that other CCS's allow zero length submissions. *sigh* -- JB String fileData = file.getData(); if(fileData == null || fileData.length() == 0) { - return Response.status(Response.Status.BAD_REQUEST).entity("no file data specified for " + fileName).build(); + // nice to put it in the log in case any questions come up. + log.info(user + " POSTing empty source submission on behalf of team " + team_id); + +// return Response.status(Response.Status.BAD_REQUEST).entity("no file data specified for " + fileName).build(); } IFile iFile = new IFileImpl(file.getFilename(), fileData); srcFiles.add(iFile); From 521e8315bca29047b09e8f8160ac720aeb2cd37d Mon Sep 17 00:00:00 2001 From: John Buck Date: Sat, 26 Jul 2025 19:50:58 -0400 Subject: [PATCH 08/14] i_1149 control throttling with configuration flag Add a configuration parameter, "submission-throttling", whose default value is "true", to the system.pc2.yaml file. This will provide a means of allowing a contest administrator to disable the throttling mechanism. This is useful during contest playback at high speeds. Remember the submission throttling flag in ContestInformation. --- .../csus/ecs/pc2/core/InternalController.java | 64 ++++++++++--------- .../pc2/core/model/ContestInformation.java | 13 ++++ .../imports/ccs/ContestSnakeYAMLLoader.java | 4 ++ .../ecs/pc2/imports/ccs/IContestLoader.java | 2 + 4 files changed, 54 insertions(+), 29 deletions(-) diff --git a/src/edu/csus/ecs/pc2/core/InternalController.java b/src/edu/csus/ecs/pc2/core/InternalController.java index abfdc2a35..2f35f9120 100644 --- a/src/edu/csus/ecs/pc2/core/InternalController.java +++ b/src/edu/csus/ecs/pc2/core/InternalController.java @@ -610,20 +610,23 @@ public void submitJudgeRun(Problem problem, Language language, SerializedFile ma //determine whether to apply the "current throttling strategy" to this submission // (i.e., whether to accept the run for submission to the PC2 Server) boolean accept = true; - Account submitterAccount = contest.getAccount(contest.getClientId()); - - if (submitterAccount.isTeam()) { - //Determine the throttling strategy to be applied to team submissions. - //The following shows several alternative strategy selections, with only one being enabled. - //A preferable extension would be to allow external (e.g. run-time) selection of the desired strategy, - // chosen from among a list of available strategies and specified by, e.g. a YAML file or an interactive - // GUI (such as the PC2 Admin) -// IThrottleStrategy strategy = new AcceptAllStrategy(); -// IThrottleStrategy strategy = new RejectAllStrategy(); - IThrottleStrategy strategy = new MaxSubmissionsPerMinuteStrategy(contest,6); -// IThrottleStrategy strategy = new MaxSubmissionsPerMinuteStrategy(contest,MaxSubmissionsPerMinuteStrategy.DEFAULT_MAX_SUBMISSIONS_PER_MINUTE); -// IThrottleStrategy strategy = new MaxSubmissionsPerMinuteStrategy(contest); //uses DEFAULT_MAX_SUBMISSIONS_PER_MINUTE; same as prev line - accept = strategy.accept(run); + + if(contest.getContestInformation().isSubmissionThrottling()) { + Account submitterAccount = contest.getAccount(contest.getClientId()); + + if (submitterAccount.isTeam()) { + //Determine the throttling strategy to be applied to team submissions. + //The following shows several alternative strategy selections, with only one being enabled. + //A preferable extension would be to allow external (e.g. run-time) selection of the desired strategy, + // chosen from among a list of available strategies and specified by, e.g. a YAML file or an interactive + // GUI (such as the PC2 Admin) + // IThrottleStrategy strategy = new AcceptAllStrategy(); + // IThrottleStrategy strategy = new RejectAllStrategy(); + IThrottleStrategy strategy = new MaxSubmissionsPerMinuteStrategy(contest,6); + // IThrottleStrategy strategy = new MaxSubmissionsPerMinuteStrategy(contest,MaxSubmissionsPerMinuteStrategy.DEFAULT_MAX_SUBMISSIONS_PER_MINUTE); + // IThrottleStrategy strategy = new MaxSubmissionsPerMinuteStrategy(contest); //uses DEFAULT_MAX_SUBMISSIONS_PER_MINUTE; same as prev line + accept = strategy.accept(run); + } } Packet packet ; @@ -5008,21 +5011,24 @@ public void submitRun(ClientId submitter, Problem problem, Language language, St // There should probably be a "bypass" flag that can be set via config file, or // a separate user created for receiving CLICS API requests. boolean accept = true; - // use the submitter account, not this client. This client is doing a proxy submit for a team. - Account submitterAccount = contest.getAccount(submitter); - - if (submitterAccount.isTeam()) { - //Determine the throttling strategy to be applied to team submissions. - //The following shows several alternative strategy selections, with only one being enabled. - //A preferable extension would be to allow external (e.g. run-time) selection of the desired strategy, - // chosen from among a list of available strategies and specified by, e.g. a YAML file or an interactive - // GUI (such as the PC2 Admin) -// IThrottleStrategy strategy = new AcceptAllStrategy(); -// IThrottleStrategy strategy = new RejectAllStrategy(); - IThrottleStrategy strategy = new MaxSubmissionsPerMinuteStrategy(contest, 6); -// IThrottleStrategy strategy = new MaxSubmissionsPerMinuteStrategy(contest,MaxSubmissionsPerMinuteStrategy.DEFAULT_MAX_SUBMISSIONS_PER_MINUTE); - - accept = strategy.accept(run); + + if(contest.getContestInformation().isSubmissionThrottling()) { + // use the submitter account, not this client. This client is doing a proxy submit for a team. + Account submitterAccount = contest.getAccount(submitter); + + if (submitterAccount.isTeam()) { + //Determine the throttling strategy to be applied to team submissions. + //The following shows several alternative strategy selections, with only one being enabled. + //A preferable extension would be to allow external (e.g. run-time) selection of the desired strategy, + // chosen from among a list of available strategies and specified by, e.g. a YAML file or an interactive + // GUI (such as the PC2 Admin) + // IThrottleStrategy strategy = new AcceptAllStrategy(); + // IThrottleStrategy strategy = new RejectAllStrategy(); + IThrottleStrategy strategy = new MaxSubmissionsPerMinuteStrategy(contest, 6); + // IThrottleStrategy strategy = new MaxSubmissionsPerMinuteStrategy(contest,MaxSubmissionsPerMinuteStrategy.DEFAULT_MAX_SUBMISSIONS_PER_MINUTE); + + accept = strategy.accept(run); + } } if (accept) { diff --git a/src/edu/csus/ecs/pc2/core/model/ContestInformation.java b/src/edu/csus/ecs/pc2/core/model/ContestInformation.java index 71d226d52..6885dbdb4 100644 --- a/src/edu/csus/ecs/pc2/core/model/ContestInformation.java +++ b/src/edu/csus/ecs/pc2/core/model/ContestInformation.java @@ -237,6 +237,11 @@ public String getType() { */ RemoteCCSInformation remoteCCSInfo[] = null; + /** + * Submission Throttling + */ + private boolean submissionThrottling = true; + /** * Returns the date/time when the contest is scheduled (intended) to start. * This value is null if no scheduled start time has been set, @@ -1076,4 +1081,12 @@ public RemoteCCSInformation getRemoteCCSInfo(String account) { } return(remoteInfo); } + public boolean isSubmissionThrottling() { + return submissionThrottling; + } + + public void setSubmissionThrottling(boolean submissionThrottling) { + this.submissionThrottling = submissionThrottling; + } + } diff --git a/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java b/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java index f61127871..b70654be2 100644 --- a/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java +++ b/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java @@ -422,6 +422,10 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S String teamScoreboadDisplayString = ContestImportUtilities.fetchValue(content, TEAM_SCOREBOARD_DISPLAY_FORMAT_STRING, contestInformation.getTeamScoreboardDisplayFormat()); contestInformation.setTeamScoreboardDisplayFormat(teamScoreboadDisplayString); + // control submission throttling + boolean submissionThrottling = fetchBooleanValue(content, SUBMISSION_THROTTLING_KEY, contestInformation.isSubmissionThrottling()); + contestInformation.setSubmissionThrottling(submissionThrottling); + // enable shadow mode boolean shadowMode = ContestImportUtilities.fetchBooleanValue(content, SHADOW_MODE_KEY, contestInformation.isShadowMode()); contestInformation.setShadowMode(shadowMode); diff --git a/src/edu/csus/ecs/pc2/imports/ccs/IContestLoader.java b/src/edu/csus/ecs/pc2/imports/ccs/IContestLoader.java index ffce2d32c..8e3dfd319 100644 --- a/src/edu/csus/ecs/pc2/imports/ccs/IContestLoader.java +++ b/src/edu/csus/ecs/pc2/imports/ccs/IContestLoader.java @@ -251,6 +251,8 @@ public interface IContestLoader { */ String TEAM_SCOREBOARD_DISPLAY_FORMAT_STRING = "team-scoreboard-display-format-string"; + String SUBMISSION_THROTTLING_KEY = "submission-throttling"; + Problem addDefaultPC2Validator(Problem problem, int optionNumber); void dumpSerialzedFileList(Problem problem, String logPrefixId, SerializedFile[] sfList); From 20eb69f53618e2751e52cc5ebf24afa631112e2d Mon Sep 17 00:00:00 2001 From: John Buck Date: Fri, 18 Jul 2025 09:27:24 -0400 Subject: [PATCH 09/14] i_1006 Fix event feed for point scoring Use Double object instead of primative. This way we don't have to filter and can just use the "only include if non-null" annotation instead of all the extra rtfilter code. Fix copyright date in ProblemDataFiles. --- .../pc2/clics/API202306/CLICSJudgement.java | 44 +--------------- .../ecs/pc2/clics/API202306/CLICSProblem.java | 30 +---------- .../clics/API202306/CLICSProblemScore.java | 50 ++----------------- .../ecs/pc2/clics/API202306/CLICSScore.java | 49 +----------------- .../pc2/clics/API202306/CLICSScoreboard.java | 2 +- .../clics/API202306/CLICSScoreboardRow.java | 7 ++- .../ecs/pc2/core/model/ProblemDataFiles.java | 2 +- 7 files changed, 14 insertions(+), 170 deletions(-) diff --git a/src/edu/csus/ecs/pc2/clics/API202306/CLICSJudgement.java b/src/edu/csus/ecs/pc2/clics/API202306/CLICSJudgement.java index 03cfe20cd..b27c51b86 100644 --- a/src/edu/csus/ecs/pc2/clics/API202306/CLICSJudgement.java +++ b/src/edu/csus/ecs/pc2/clics/API202306/CLICSJudgement.java @@ -3,17 +3,11 @@ import java.util.Calendar; import java.util.Date; -import java.util.HashSet; import java.util.Set; import java.util.logging.Level; -import com.fasterxml.jackson.annotation.JsonFilter; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ser.FilterProvider; -import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; -import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; import edu.csus.ecs.pc2.core.IInternalController; import edu.csus.ecs.pc2.core.Utilities; @@ -22,7 +16,6 @@ import edu.csus.ecs.pc2.core.model.JudgementRecord; import edu.csus.ecs.pc2.core.model.Run; import edu.csus.ecs.pc2.core.util.IJSONTool; -import edu.csus.ecs.pc2.services.core.JSONUtilities; /** * Contains the judgment for a submission (Accepted, Wrong Answer, etc). @@ -32,7 +25,6 @@ * */ @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonFilter("rtFilter") public class CLICSJudgement { @JsonProperty @@ -45,7 +37,7 @@ public class CLICSJudgement { private String judgement_type_id; @JsonProperty - private double score; + private Double score; @JsonProperty private String start_time; @@ -62,8 +54,6 @@ public class CLICSJudgement { @JsonProperty private double max_run_time; - private boolean isPointScoring = false; - /** * Fill in properties for a judgment description. * @@ -78,9 +68,6 @@ public CLICSJudgement(IInternalContest model, IInternalController controller, Ru id = submission.getElementId().toString(); submission_id = IJSONTool.getSubmissionId(submission); - // Remember this for serialization - isPointScoring = model.getContestInformation().isScoreboardTypeScore(); - Date startJudgeDate = submission.getJudgeStartDate(); if(startJudgeDate == null) { // Yikes! Using submission time since there is no judge start date... Something is amiss, but this is a last ditch guess @@ -143,33 +130,4 @@ public CLICSJudgement(IInternalContest model, IInternalController controller, Ru } // else not much to do here if no start date. } - - public String toJSON() { - Set exceptProps = new HashSet(); - - getExceptProps(exceptProps); - try { - ObjectMapper mapper = JSONUtilities.getObjectMapper(); - // for this judgment, create filter to omit inappropriate properties, - // 'score' in this case if not Point Scoring contest - SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.serializeAllExcept(exceptProps); - FilterProvider fp = new SimpleFilterProvider().addFilter("rtFilter", filter).setFailOnUnknownId(false); - mapper.setFilters(fp); - return mapper.writeValueAsString(this); - } catch (Exception e) { - return "Error creating JSON for judgment " + e.getMessage(); - } - } - - /** - * Get set of properties for which we do not want to serialize into JSON. - * This is so we don't serialize score for pass-fail contests - * - * @param exceptProps Set to fill in with property names to omit - */ - public void getExceptProps(Set exceptProps) { - if(!isPointScoring){ - exceptProps.add("score"); - } - } } diff --git a/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblem.java b/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblem.java index fab971907..5a24b1f06 100644 --- a/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblem.java +++ b/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblem.java @@ -1,16 +1,9 @@ // Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. package edu.csus.ecs.pc2.clics.API202306; -import java.util.HashSet; -import java.util.Set; - -import com.fasterxml.jackson.annotation.JsonFilter; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ser.FilterProvider; -import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; -import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; import edu.csus.ecs.pc2.core.StringUtilities; import edu.csus.ecs.pc2.core.model.IInternalContest; @@ -28,7 +21,6 @@ * */ @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonFilter("rtFilter") public class CLICSProblem { @JsonProperty @@ -59,7 +51,7 @@ public class CLICSProblem { private int test_data_count; @JsonProperty - private double max_score; + private Double max_score; // The next two will be 'null' for now until we implement the new json CPF @JsonProperty("package") @@ -121,31 +113,11 @@ public CLICSProblem(IInternalContest model, Problem problem, int ordinal) { } public String toJSON() { - Set exceptProps = new HashSet(); - - getExceptProps(exceptProps); try { ObjectMapper mapper = JSONUtilities.getObjectMapper(); - // for this problem, create filter to omit inappropriate properties, - // 'max_score' in this case if not Point Scoring contest - SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.serializeAllExcept(exceptProps); - FilterProvider fp = new SimpleFilterProvider().addFilter("rtFilter", filter).setFailOnUnknownId(false); - mapper.setFilters(fp); return mapper.writeValueAsString(this); } catch (Exception e) { return "Error creating JSON for CLICSProblem " + e.getMessage(); } } - - /** - * Get set of properties for which we do not want to serialize into JSON. - * This is so we don't serialize max_score for pass-fail contests - * - * @param exceptProps Set to fill in with property names to omit - */ - public void getExceptProps(Set exceptProps) { - if(!isPointScoring){ - exceptProps.add("max_score"); - } - } } diff --git a/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblemScore.java b/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblemScore.java index daaffe212..70cdf720d 100644 --- a/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblemScore.java +++ b/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblemScore.java @@ -2,22 +2,17 @@ package edu.csus.ecs.pc2.clics.API202306; import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; import com.fasterxml.jackson.annotation.JsonCreator; +<<<<<<< HEAD import com.fasterxml.jackson.annotation.JsonInclude; +======= +>>>>>>> dc2ae230a (i_1006 Fix event feed for point scoring) import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ser.FilterProvider; -import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; -import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; import edu.csus.ecs.pc2.core.StringUtilities; import edu.csus.ecs.pc2.core.Utilities; -import edu.csus.ecs.pc2.core.model.IInternalContest; import edu.csus.ecs.pc2.core.standings.ProblemSummaryInfo; -import edu.csus.ecs.pc2.services.core.JSONUtilities; /** * Contains information about the score a team received for a single problem. @@ -29,7 +24,6 @@ */ @JsonInclude(JsonInclude.Include.NON_NULL) - public class CLICSProblemScore { @JsonProperty @@ -45,13 +39,11 @@ public class CLICSProblemScore { private boolean solved; @JsonProperty - private double score; + private Double score; @JsonProperty private int time; - private boolean isPointScoring = false; - /** * Provide empty constructor for Jackson deserialization */ @@ -66,7 +58,7 @@ public CLICSProblemScore() { * @param probEleToShort hashmap for mapping problem elementid to shortname * @param versionInfo */ - public CLICSProblemScore(IInternalContest model, HashMap probEleToShort, ProblemSummaryInfo psi) { + public CLICSProblemScore(HashMap probEleToShort, ProblemSummaryInfo psi) { num_judged = Utilities.nullSafeToInt(psi.getAttempts(), 0); num_pending = Utilities.nullSafeToInt(psi.getIsPending(), 0); problem_id = psi.getProblemId(); @@ -88,38 +80,6 @@ public CLICSProblemScore(IInternalContest model, HashMap probEle } } } - if(model != null) { - isPointScoring = model.getContestInformation().isScoreboardTypeScore(); - } - } - - public String toJSON() { - Set exceptProps = new HashSet(); - - getExceptProps(exceptProps); - try { - ObjectMapper mapper = JSONUtilities.getObjectMapper(); - // for this problem's score, create filter to omit inappropriate properties, - // 'score' in this case if not Point Scoring contest - SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.serializeAllExcept(exceptProps); - FilterProvider fp = new SimpleFilterProvider().addFilter("rtFilter", filter).setFailOnUnknownId(false); - mapper.setFilters(fp); - return mapper.writeValueAsString(this); - } catch (Exception e) { - return "Error creating JSON for CLICSProblemScore " + e.getMessage(); - } - } - - /** - * Get set of properties for which we do not want to serialize into JSON. - * This is so we don't serialize score for pass-fail contests - * - * @param exceptProps Set to fill in with property names to omit - */ - public void getExceptProps(Set exceptProps) { - if(!isPointScoring){ - exceptProps.add("score"); - } } /** diff --git a/src/edu/csus/ecs/pc2/clics/API202306/CLICSScore.java b/src/edu/csus/ecs/pc2/clics/API202306/CLICSScore.java index 4e9525f53..ba278ca43 100644 --- a/src/edu/csus/ecs/pc2/clics/API202306/CLICSScore.java +++ b/src/edu/csus/ecs/pc2/clics/API202306/CLICSScore.java @@ -1,22 +1,12 @@ // Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. package edu.csus.ecs.pc2.clics.API202306; -import java.util.HashSet; -import java.util.Set; - import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonFilter; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.ser.FilterProvider; -import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter; -import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider; import edu.csus.ecs.pc2.core.Utilities; -import edu.csus.ecs.pc2.core.model.IInternalContest; import edu.csus.ecs.pc2.core.standings.TeamStanding; -import edu.csus.ecs.pc2.services.core.JSONUtilities; /** * Contains information about the score for a team on the scoreboard. @@ -25,7 +15,6 @@ * */ @JsonInclude(JsonInclude.Include.NON_NULL) -@JsonFilter("rtFilter") public class CLICSScore { @JsonProperty @@ -35,13 +24,11 @@ public class CLICSScore { private int total_time; @JsonProperty - private double score; + private Double score; @JsonProperty private int time; - private boolean isPointScoring = false; - /** * Provide empty constructor for Jackson deserialization */ @@ -56,7 +43,7 @@ public CLICSScore() { * @param teamStanding The team's scoring information * @throws NumberFormatException if bad scores are in the standings */ - public CLICSScore(IInternalContest model, TeamStanding teamStanding) { + public CLICSScore(TeamStanding teamStanding) { num_solved = Utilities.nullSafeToInt(teamStanding.getSolved(), 0); total_time = Utilities.nullSafeToInt(teamStanding.getPoints(), 0); if(num_solved > 0) { @@ -64,39 +51,8 @@ public CLICSScore(IInternalContest model, TeamStanding teamStanding) { time = Integer.parseInt(teamStanding.getLastSolved()); score = Double.parseDouble(teamStanding.getScore()); } - if(model != null) { - isPointScoring = model.getContestInformation().isScoreboardTypeScore(); - } - } - - public String toJSON() { - Set exceptProps = new HashSet(); - - getExceptProps(exceptProps); - try { - ObjectMapper mapper = JSONUtilities.getObjectMapper(); - // for this team score, create filter to omit inappropriate properties, - // 'score' in this case if not Point Scoring contest - SimpleBeanPropertyFilter filter = SimpleBeanPropertyFilter.serializeAllExcept(exceptProps); - FilterProvider fp = new SimpleFilterProvider().addFilter("rtFilter", filter).setFailOnUnknownId(false); - mapper.setFilters(fp); - return mapper.writeValueAsString(this); - } catch (Exception e) { - return "Error creating JSON for CLICSScore " + e.getMessage(); - } } - /** - * Get set of properties for which we do not want to serialize into JSON. - * This is so we don't serialize score for pass-fail contests - * - * @param exceptProps Set to fill in with property names to omit - */ - public void getExceptProps(Set exceptProps) { - if(!isPointScoring){ - exceptProps.add("score"); - } - } public int getNum_solved() { return num_solved; @@ -113,7 +69,6 @@ public int getTime() { /** * @return the score */ - public Double getScore() { return score; } diff --git a/src/edu/csus/ecs/pc2/clics/API202306/CLICSScoreboard.java b/src/edu/csus/ecs/pc2/clics/API202306/CLICSScoreboard.java index c47e70618..9b7e23787 100644 --- a/src/edu/csus/ecs/pc2/clics/API202306/CLICSScoreboard.java +++ b/src/edu/csus/ecs/pc2/clics/API202306/CLICSScoreboard.java @@ -117,7 +117,7 @@ public CLICSScoreboard(IInternalContest model, Group group, Integer division) t List standings = contestStandings.getTeamStandings(); if(standings != null) { for (TeamStanding teamStanding : standings) { - rowsArray.add(new CLICSScoreboardRow(model, probEleToShortName, teamStanding)); + rowsArray.add(new CLICSScoreboardRow(probEleToShortName, teamStanding)); } rows = rowsArray.toArray(new CLICSScoreboardRow[0]); } else { diff --git a/src/edu/csus/ecs/pc2/clics/API202306/CLICSScoreboardRow.java b/src/edu/csus/ecs/pc2/clics/API202306/CLICSScoreboardRow.java index aff16d992..cedac4218 100644 --- a/src/edu/csus/ecs/pc2/clics/API202306/CLICSScoreboardRow.java +++ b/src/edu/csus/ecs/pc2/clics/API202306/CLICSScoreboardRow.java @@ -11,7 +11,6 @@ import com.fasterxml.jackson.annotation.JsonProperty; import edu.csus.ecs.pc2.core.Utilities; -import edu.csus.ecs.pc2.core.model.IInternalContest; import edu.csus.ecs.pc2.core.standings.ProblemSummaryInfo; import edu.csus.ecs.pc2.core.standings.TeamStanding; @@ -51,15 +50,15 @@ public CLICSScoreboardRow() { * @param probEleToShortName hashmap for mapping problem elementid to shortname * @param teamStanding xml representation of the standings for a team */ - public CLICSScoreboardRow(IInternalContest model, HashMap probEleToShortName, TeamStanding teamStanding) { + public CLICSScoreboardRow(HashMap probEleToShortName, TeamStanding teamStanding) { team_id = teamStanding.getTeamId(); rank = Utilities.nullSafeToInt(teamStanding.getRank(), 0); - score = new CLICSScore(model, teamStanding); + score = new CLICSScore(teamStanding); ArrayList pslist = new ArrayList(); for( ProblemSummaryInfo psi : teamStanding.getProblemSummaryInfos()) { - pslist.add(new CLICSProblemScore(model, probEleToShortName, psi)); + pslist.add(new CLICSProblemScore(probEleToShortName, psi)); } problems = pslist.toArray(new CLICSProblemScore[0]); } diff --git a/src/edu/csus/ecs/pc2/core/model/ProblemDataFiles.java b/src/edu/csus/ecs/pc2/core/model/ProblemDataFiles.java index e150c92a6..63e859573 100644 --- a/src/edu/csus/ecs/pc2/core/model/ProblemDataFiles.java +++ b/src/edu/csus/ecs/pc2/core/model/ProblemDataFiles.java @@ -1,4 +1,4 @@ -// Copyright (C) 1989-2019 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. +// Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. package edu.csus.ecs.pc2.core.model; import java.io.File; From 62c72ff988a1994814df51cef65e5a768ddd8c48 Mon Sep 17 00:00:00 2001 From: John Buck Date: Tue, 29 Jul 2025 20:24:25 -0400 Subject: [PATCH 10/14] i_1006 Bug fixes to legacy grader Legacy Grader did not generate proper results for WA/RTE/TLE (non-AC). It was printing them instead of putting them in a string. Added optional debug logging to a specified file. --- .../csus/ecs/pc2/graders/LegacyGrader.java | 48 +++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/src/edu/csus/ecs/pc2/graders/LegacyGrader.java b/src/edu/csus/ecs/pc2/graders/LegacyGrader.java index 9ddb9d5ee..60a1942fd 100644 --- a/src/edu/csus/ecs/pc2/graders/LegacyGrader.java +++ b/src/edu/csus/ecs/pc2/graders/LegacyGrader.java @@ -1,6 +1,7 @@ // Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau. package edu.csus.ecs.pc2.graders; +import java.io.PrintWriter; import java.util.ArrayList; import java.util.Scanner; @@ -52,6 +53,22 @@ enum JudgmentCodes { private boolean ignoreSample = false; private int graderError = 0; + private String logFile = null; + private PrintWriter debugStream = null; + + public LegacyGrader() { + + } + + public LegacyGrader(String logFile) { + this.logFile = logFile; + try { + debugStream = new PrintWriter(logFile, "UTF-8"); + } catch(Exception e) { + System.err.println("LegacyGrader: Can not crete debug log file " + logFile + ": " + e); + } + } + /** * See if the supplied string argument is a valid verdict mode. * @@ -170,9 +187,22 @@ public String gradeTestCases(ArrayList testCaseResults) { String firstError = null; String graderResult = null; + if(debugStream != null) { + debugStream.println("Grader Settings:"); + debugStream.println(" verdictMode = " + verdictMode); + debugStream.println(" scoringMode = " + scoringMode); + debugStream.println(" acceptIfAnyAccepted = " + acceptIfAnyAccepted); + debugStream.println(" ignoreSample = " + ignoreSample); + debugStream.println("TestCases:"); + } + graderError = 0; for(String line : testCaseResults) { nLine++; + if(debugStream != null) { + debugStream.printf("%3d:%s", nLine, line); + debugStream.println(); + } /* * A little explanation here about ignoring samples. * The Legacy Grader specification says: @@ -193,6 +223,9 @@ public String gradeTestCases(ArrayList testCaseResults) { break; } ignoreSampleGroup = false; + if(debugStream != null) { + debugStream.println("Ignored previous line due to ignore_sample"); + } continue; } String [] values = line.trim().split("\\s+"); @@ -278,7 +311,7 @@ public String gradeTestCases(ArrayList testCaseResults) { } } else { // determine non-accepted judgment - // All cases should either print the correct output to stdout, or print + // All cases should either save the correct output to graderResult, or print // an error to stderr and set graderError to a non-zero value. switch(verdictMode) { case worst_error: @@ -286,7 +319,7 @@ public String gradeTestCases(ArrayList testCaseResults) { for(JudgmentCodes jcode : JudgmentCodes.values()) { idx = jcode.ordinal(); if(idx > 0 && sawJudgment[idx]) { - System.out.println(jcode.toString() + " 0"); + graderResult = jcode.toString() + " 0"; found = true; break; } @@ -304,7 +337,7 @@ public String gradeTestCases(ArrayList testCaseResults) { System.err.println("LegacyGrader: FATAL error - can not find judgment code for first_error mode."); graderError = GRADER_ERROR_BAD_FIRST_CODE; } else { - System.out.println(firstError + " 0"); + graderResult = firstError + " 0"; } break; @@ -316,6 +349,15 @@ public String gradeTestCases(ArrayList testCaseResults) { } } } + if(debugStream != null) { + debugStream.println(); + if(graderResult != null) { + debugStream.println("RESULT:" + graderResult); + } else { + debugStream.println("ERROR: Grader error " + graderError); + } + debugStream.flush(); + } // this will be null in the case of an error, in which case graderError will have the error code // in the case of success, this will be the "judgment_acronym score", eg. "AC 50" return graderResult; From 7cb35afbf194a99d3dccf5c21db639ee367a33d5 Mon Sep 17 00:00:00 2001 From: John Buck Date: Tue, 29 Jul 2025 20:25:29 -0400 Subject: [PATCH 11/14] i_1006 Fix merge error Fix merge error when throttling was cherry-picked. (ContestImportUtilities) --- src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java b/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java index b70654be2..27ed684cb 100644 --- a/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java +++ b/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java @@ -423,7 +423,7 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S contestInformation.setTeamScoreboardDisplayFormat(teamScoreboadDisplayString); // control submission throttling - boolean submissionThrottling = fetchBooleanValue(content, SUBMISSION_THROTTLING_KEY, contestInformation.isSubmissionThrottling()); + boolean submissionThrottling = ContestImportUtilities.fetchBooleanValue(content, SUBMISSION_THROTTLING_KEY, contestInformation.isSubmissionThrottling()); contestInformation.setSubmissionThrottling(submissionThrottling); // enable shadow mode From 6cbfba695986518f0298eabdd080fafe299db0b1 Mon Sep 17 00:00:00 2001 From: John Buck Date: Tue, 29 Jul 2025 20:26:25 -0400 Subject: [PATCH 12/14] i_1006 Fix merge error on CLICSProblemScore Import conflict when merging cherry-picked commit fixed. --- src/edu/csus/ecs/pc2/clics/API202306/CLICSProblemScore.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblemScore.java b/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblemScore.java index 70cdf720d..f4001544b 100644 --- a/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblemScore.java +++ b/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblemScore.java @@ -4,10 +4,7 @@ import java.util.HashMap; import com.fasterxml.jackson.annotation.JsonCreator; -<<<<<<< HEAD import com.fasterxml.jackson.annotation.JsonInclude; -======= ->>>>>>> dc2ae230a (i_1006 Fix event feed for point scoring) import com.fasterxml.jackson.annotation.JsonProperty; import edu.csus.ecs.pc2.core.StringUtilities; From 5a9b5524cc8151f977c10b7dbf115eba841a351c Mon Sep 17 00:00:00 2001 From: John Buck Date: Tue, 29 Jul 2025 20:27:59 -0400 Subject: [PATCH 13/14] i_1006 Bug fixes for scoring problems in Executable Add "on_reject" "break" support as per spec. Fix some comments to mention some concerns. Handle null return values on test data group grading. Create grader debug logs for every test data group. --- .../csus/ecs/pc2/core/execute/Executable.java | 35 ++++++++++++++++--- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/edu/csus/ecs/pc2/core/execute/Executable.java b/src/edu/csus/ecs/pc2/core/execute/Executable.java index 94573fe53..7849b913b 100644 --- a/src/edu/csus/ecs/pc2/core/execute/Executable.java +++ b/src/edu/csus/ecs/pc2/core/execute/Executable.java @@ -862,6 +862,9 @@ private void writeGraderResultsToFile(Run run, String graderResultFileName) { private String getPointScoringRunResult(Run run) { //"getTestDataGroupResult()" == "recurse(tdg)" + // This may not be sufficient. We want to be sure we start at the root. testcases[0] should be + // "sample" and its parent should be root. We should probably put a loop here to walk back up until + // getParent() == null, then use that TDG. But, I think this SHOULD be ok? -- JB String runResult = getTestDataGroupResults(run.getRunTestCases()[0].getTestDataGroup().getParent()); return runResult; } @@ -882,6 +885,7 @@ private String getTestDataGroupResults(TestDataGroup tdg) { //a list of the test case results associated with the specified test data group ArrayList testCaseResultList = new ArrayList() ; + boolean breakOnReject = tdg.isOnRejectBreak(); //get the results for the test cases directly declared in the test data group and add them to the list ArrayList groupTestCaseResults = getGroupTestCaseResults(tdg); @@ -889,14 +893,30 @@ private String getTestDataGroupResults(TestDataGroup tdg) { testCaseResultList.add(testCaseResult); } - //recursively get the results for test cases declared as children of the specified test data group and add them to the list - for (TestDataGroup child : tdg.getTestDataGroups()) { - String childResult = getTestDataGroupResults(child); - testCaseResultList.add(childResult); + // if we are not breaking on reject, or there are no test cases in this groups level, or, the last case on the list + // is accepted, then, we have to get the results of each sub group and add to the list. + // Note that getGroupTestCaseResults() will return on the first failed case if on_reject = break, and, the last + // result in the list will be the failed result (non-AC) + if(!breakOnReject || testCaseResultList.isEmpty() || + testCaseResultList.get(testCaseResultList.size()-1).split("\\s+")[0].equalsIgnoreCase(CLICS_JUDGEMENT_ACRONYM.AC.toString())){ + + //recursively get the results for test cases declared as children of the specified test data group and add them to the list + for (TestDataGroup child : tdg.getTestDataGroups()) { + String childResult = getTestDataGroupResults(child); + if(childResult == null) { + log.log(Log.WARNING, "Grader childResult was null for test data group: '" + child.getGroupName() + "' in group '" + tdg.getGroupName() + "'"); + return(null); + } + testCaseResultList.add(childResult); + // If we are supposed to break on reject, and any of the sub groups return a non-AC then stop adding subgroups and proceed to grading. + if(breakOnReject && !childResult.split("\\s+")[0].equalsIgnoreCase(CLICS_JUDGEMENT_ACRONYM.AC.toString())) { + break; + } + } } //we've recursed to the lowest level in the test case tree; create a Grader to get a Result (acronym and score) for this level - LegacyGrader grader = new LegacyGrader(); + LegacyGrader grader = new LegacyGrader(prefixExecuteDirname("graderLog-" + tdg.getGroupName().replace(File.separator, "_") + ".txt")); //set the arguments for the grader based on the grader flags in the currently specified TestDataGroup. //TODO: it seems like there SHOULD be separate "scoringMode" and "verdictMode" attributes defined in a TestDataGroup -- , @@ -956,6 +976,7 @@ private String getTestDataGroupResults(TestDataGroup tdg) { private ArrayList getGroupTestCaseResults(TestDataGroup tdg) { //a list of test cases declared directly in the specfied TestDataGroup ArrayList tdgTestCaseResults = new ArrayList(); + boolean breakOnReject = tdg.isOnRejectBreak(); //check every test case in the run for (RunTestCase testCase : run.getRunTestCases()) { @@ -964,6 +985,10 @@ private ArrayList getGroupTestCaseResults(TestDataGroup tdg) { //yes, the test case belongs to the test data group; add its result to the list String testCaseResult = testCase.getJudgementAcronym().toString() + " " + testCase.getScore(); tdgTestCaseResults.add(testCaseResult); + // Stop adding to list on first failed case, if that's what is wanted. + if(breakOnReject && !testCase.isPassed()) { + break; + } } } return tdgTestCaseResults; From ed1b5fb535095e2f20b74f6ab9639263e549b219 Mon Sep 17 00:00:00 2001 From: John Buck Date: Fri, 1 Aug 2025 07:40:11 -0400 Subject: [PATCH 14/14] i_1006 Junit found error in new code Do not set scoreboard if it's not specify in the YAML config file. The default will be pass-fail if it's not specified. It was being set to "null" which was causing an NPE in the JUnit. --- .../pc2/imports/ccs/ContestSnakeYAMLLoader.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java b/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java index 27ed684cb..89b95b101 100644 --- a/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java +++ b/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java @@ -628,15 +628,17 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S // If the contest type is present in contest.yaml, verify it String scoreType = ContestImportUtilities.fetchValue(content, CLICS_CONTEST_SCOREBOARD_TYPE); - if(scoreType != null && !scoreType.equals(CLICS_CONTEST_SCOREBOARD_TYPE_PASSFAIL) + if(scoreType != null) { + if(!scoreType.equals(CLICS_CONTEST_SCOREBOARD_TYPE_PASSFAIL) && !scoreType.equals(CLICS_CONTEST_SCOREBOARD_TYPE_SCORE)) { - throw new YamlLoadException("Invalid " + CLICS_CONTEST_SCOREBOARD_TYPE + ": " - + scoreType + ", expected " - + CLICS_CONTEST_SCOREBOARD_TYPE_PASSFAIL - + " or " - + CLICS_CONTEST_SCOREBOARD_TYPE_SCORE); + throw new YamlLoadException("Invalid " + CLICS_CONTEST_SCOREBOARD_TYPE + ": " + + scoreType + ", expected " + + CLICS_CONTEST_SCOREBOARD_TYPE_PASSFAIL + + " or " + + CLICS_CONTEST_SCOREBOARD_TYPE_SCORE); + } + setContestScoreboardType(contest, scoreType); } - setContestScoreboardType(contest, scoreType); Object privatehtmlOutputDirectory = ContestImportUtilities.fetchObjectValue(content, OUTPUT_PRIVATE_SCORE_DIR_KEY); if (privatehtmlOutputDirectory != null) {