diff --git a/samps/contests/pointscoring/README.txt b/samps/contests/pointscoring/README.txt
new file mode 100644
index 000000000..1f25f4a93
--- /dev/null
+++ b/samps/contests/pointscoring/README.txt
@@ -0,0 +1,40 @@
+This folder contains the configuration for a PC2 sample "point scoring" contest.
+
+The contest has a "problemset.yaml" file containing descriptions of three problems: 'a', 'b', and 'c'.
+The intent is to (eventually) configure a variety of different problems, each with different "scoring properties".
+Currently however, problem folders 'b' and 'c' contain duplicates of problem 'a' (except with the appropriate problem names changed).
+
+The intent is to expand the contest by modifying the scoring attributes of problems 'b' and 'c' so that they test various other
+ "point scoring" attribute combinations. (Eventually of course there may be additional combinations, requiring addition of new problems 'd', 'e', etc.)
+
+All problems are intended to be based on the PC2 "Sumit" problem ("read input integers and print the sum of the positive integers").
+This allows for easy testing, since there already exist correct and incorrect solutions to Sumit (in several languages) in the PC2 "samps/src" folder.
+
+Problem 'a' has the following attributes:
+ - a problem_statement folder with a "problem.tex" file describing the PC2 "Sumit" problem.
+ - a problem.yaml file stating that the problem named "a" is "type:scoring".
+ - a submissions folder containing accepted, "partially accepted" and "wrong answer" solutions for the Sumit problem.
+ (The "partially accepted" submission prints the sum of ALL integers in the input; i.e., it fails to ignore negative integers.
+ This could be used for example to give "partial credit" for such a solution.)
+ - both "sample" and "secret" data folders, but only with test data (.in/.ans pairs) at the root level (i.e., no scoring subgroups)
+ - no "testdata.yaml" files at any level -- hence, tests the premise that if there is no testdata.yaml file, then a "testdata.yaml"
+ file is implicitly added to the root ("data") group (see https://www.kattis.com/problem-package-format/spec/legacy.html#test-data-groups)
+ with default values, which are:
+ - on_reject: break
+ - grading: default
+ - grader_flags: "" (i.e. the empty string)
+ - input_validator_flags: ""
+ - output_validator_flags: ""
+ - accept_score: 1.0
+ - reject_score: 0.0
+ - range: -inf +inf
+ - Since "grader_flags" defaults to the empty string, the Grader will implicitly use the following default values
+ (see https://www.kattis.com/problem-package-format/spec/legacy.html#default-grader-specification):
+ - verdict mode: worst_error
+ - scoring mode: sum
+
+Problems 'b' and 'c' are currently just duplicates of problem 'a'; they need to be modified to test other point-scoring combinations
+ (for example, the use of scoring subgroups; the use of different grader flags, etc.)
+
+
+
\ No newline at end of file
diff --git a/samps/contests/pointscoring/config/a/data/sample/01.ans b/samps/contests/pointscoring/config/a/data/sample/01.ans
new file mode 100644
index 000000000..15f395758
--- /dev/null
+++ b/samps/contests/pointscoring/config/a/data/sample/01.ans
@@ -0,0 +1 @@
+The sum of the integers is 75
diff --git a/samps/contests/pointscoring/config/a/data/sample/01.in b/samps/contests/pointscoring/config/a/data/sample/01.in
new file mode 100644
index 000000000..ad7b842a9
--- /dev/null
+++ b/samps/contests/pointscoring/config/a/data/sample/01.in
@@ -0,0 +1,3 @@
+25
+50
+0
diff --git a/samps/contests/pointscoring/config/a/data/secret/02.ans b/samps/contests/pointscoring/config/a/data/secret/02.ans
new file mode 100644
index 000000000..15f395758
--- /dev/null
+++ b/samps/contests/pointscoring/config/a/data/secret/02.ans
@@ -0,0 +1 @@
+The sum of the integers is 75
diff --git a/samps/contests/pointscoring/config/a/data/secret/02.in b/samps/contests/pointscoring/config/a/data/secret/02.in
new file mode 100644
index 000000000..f5669d925
--- /dev/null
+++ b/samps/contests/pointscoring/config/a/data/secret/02.in
@@ -0,0 +1,4 @@
+25
+50
+-25
+0
diff --git a/samps/contests/pointscoring/config/a/data/secret/03.ans b/samps/contests/pointscoring/config/a/data/secret/03.ans
new file mode 100644
index 000000000..1f1acd032
--- /dev/null
+++ b/samps/contests/pointscoring/config/a/data/secret/03.ans
@@ -0,0 +1 @@
+The sum of the integers is 50
diff --git a/samps/contests/pointscoring/config/a/data/secret/03.in b/samps/contests/pointscoring/config/a/data/secret/03.in
new file mode 100644
index 000000000..29643f4ed
--- /dev/null
+++ b/samps/contests/pointscoring/config/a/data/secret/03.in
@@ -0,0 +1,4 @@
+25
+25
+-25
+0
diff --git a/samps/contests/pointscoring/config/a/problem.yaml b/samps/contests/pointscoring/config/a/problem.yaml
new file mode 100644
index 000000000..21ab2ae25
--- /dev/null
+++ b/samps/contests/pointscoring/config/a/problem.yaml
@@ -0,0 +1,12 @@
+# Point Scoring problem configuration, problem 'a', version 1.0
+---
+
+name: a
+type: scoring
+
+limits:
+ timeout: 1
+
+input:
+ readFromSTDIN: true
+
diff --git a/samps/contests/pointscoring/config/a/problem_statement/problem.tex b/samps/contests/pointscoring/config/a/problem_statement/problem.tex
new file mode 100644
index 000000000..f2b891eb6
--- /dev/null
+++ b/samps/contests/pointscoring/config/a/problem_statement/problem.tex
@@ -0,0 +1,7 @@
+\problemtitle{Sumit a}
+
+\begin{document
+\section{Problem Statement}
+Write a program which reads a series of integers from stdin and prints a message "The sum of the integers is XXX", where XXX is the sum of the positive
+integers in the input.
+\end{document}
\ No newline at end of file
diff --git a/samps/contests/pointscoring/config/a/submissions/accepted/ISumit.java b/samps/contests/pointscoring/config/a/submissions/accepted/ISumit.java
new file mode 100644
index 000000000..38447a7a6
--- /dev/null
+++ b/samps/contests/pointscoring/config/a/submissions/accepted/ISumit.java
@@ -0,0 +1,29 @@
+
+// Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau.
+import java.io.*;
+
+//
+// File: ISumit.java
+// Purpose: to print the sum of the positive integers from stdin
+
+public class ISumit {
+ public static void main(String[] args) {
+ try {
+ BufferedReader br = new BufferedReader(new InputStreamReader(System.in), 1);
+
+ String line;
+ int sum = 0;
+ int rv = 0;
+ while ((line = br.readLine()) != null) {
+ rv = new Integer(line.trim()).intValue();
+ if (rv > 0)
+ sum = sum + rv;
+ }
+ System.out.print("The sum of the integers is ");
+ System.out.println(sum);
+ } catch (Exception e) {
+ System.out.println("Possible trouble reading stdin");
+ System.out.println("Message: " + e.getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/samps/contests/pointscoring/config/a/submissions/partially_accepted/ISumitPartial.java b/samps/contests/pointscoring/config/a/submissions/partially_accepted/ISumitPartial.java
new file mode 100644
index 000000000..1574be0a7
--- /dev/null
+++ b/samps/contests/pointscoring/config/a/submissions/partially_accepted/ISumitPartial.java
@@ -0,0 +1,28 @@
+
+// Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau.
+import java.io.*;
+
+//
+// File: ISumitPartial.java
+// Purpose: to print the sum of the integers from stdin but failing to ignore negative numbers
+
+public class ISumitPartial {
+ public static void main(String[] args) {
+ try {
+ BufferedReader br = new BufferedReader(new InputStreamReader(System.in), 1);
+
+ String line;
+ int sum = 0;
+ int rv = 0;
+ while ((line = br.readLine()) != null) {
+ rv = new Integer(line.trim()).intValue();
+ sum = sum + rv;
+ }
+ System.out.print("The sum of the integers is ");
+ System.out.println(sum);
+ } catch (Exception e) {
+ System.out.println("Possible trouble reading stdin");
+ System.out.println("Message: " + e.getMessage());
+ }
+ }
+}
diff --git a/samps/contests/pointscoring/config/a/submissions/wrong_answer/ISumitWA.java b/samps/contests/pointscoring/config/a/submissions/wrong_answer/ISumitWA.java
new file mode 100644
index 000000000..3c6030fe4
--- /dev/null
+++ b/samps/contests/pointscoring/config/a/submissions/wrong_answer/ISumitWA.java
@@ -0,0 +1,30 @@
+
+// Copyright (C) 1989-2019 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau.
+import java.io.*;
+
+//
+// File: ISumitWA.java
+// Purpose: to sum the integers from stdin but print an incorrect sum
+// (that is, to fail to correctly print the sum of the positive integers from stdin)
+
+public class ISumitWA {
+ public static void main(String[] args) {
+ try {
+ BufferedReader br = new BufferedReader(new InputStreamReader(System.in), 1);
+
+ String line;
+ int sum = 0;
+ int rv = 0;
+ while ((line = br.readLine()) != null) {
+ rv = new Integer(line.trim()).intValue();
+ if (rv > 0)
+ sum = sum + rv;
+ }
+ System.out.print("Dumn integers is ");
+ System.out.println(sum + 1024);
+ } catch (Exception e) {
+ System.out.println("Possible trouble reading stdin");
+ System.out.println("Message: " + e.getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/samps/contests/pointscoring/config/b/data/sample/01.ans b/samps/contests/pointscoring/config/b/data/sample/01.ans
new file mode 100644
index 000000000..15f395758
--- /dev/null
+++ b/samps/contests/pointscoring/config/b/data/sample/01.ans
@@ -0,0 +1 @@
+The sum of the integers is 75
diff --git a/samps/contests/pointscoring/config/b/data/sample/01.in b/samps/contests/pointscoring/config/b/data/sample/01.in
new file mode 100644
index 000000000..ad7b842a9
--- /dev/null
+++ b/samps/contests/pointscoring/config/b/data/sample/01.in
@@ -0,0 +1,3 @@
+25
+50
+0
diff --git a/samps/contests/pointscoring/config/b/data/secret/02.ans b/samps/contests/pointscoring/config/b/data/secret/02.ans
new file mode 100644
index 000000000..15f395758
--- /dev/null
+++ b/samps/contests/pointscoring/config/b/data/secret/02.ans
@@ -0,0 +1 @@
+The sum of the integers is 75
diff --git a/samps/contests/pointscoring/config/b/data/secret/02.in b/samps/contests/pointscoring/config/b/data/secret/02.in
new file mode 100644
index 000000000..f5669d925
--- /dev/null
+++ b/samps/contests/pointscoring/config/b/data/secret/02.in
@@ -0,0 +1,4 @@
+25
+50
+-25
+0
diff --git a/samps/contests/pointscoring/config/b/data/secret/03.ans b/samps/contests/pointscoring/config/b/data/secret/03.ans
new file mode 100644
index 000000000..1f1acd032
--- /dev/null
+++ b/samps/contests/pointscoring/config/b/data/secret/03.ans
@@ -0,0 +1 @@
+The sum of the integers is 50
diff --git a/samps/contests/pointscoring/config/b/data/secret/03.in b/samps/contests/pointscoring/config/b/data/secret/03.in
new file mode 100644
index 000000000..29643f4ed
--- /dev/null
+++ b/samps/contests/pointscoring/config/b/data/secret/03.in
@@ -0,0 +1,4 @@
+25
+25
+-25
+0
diff --git a/samps/contests/pointscoring/config/b/problem.yaml b/samps/contests/pointscoring/config/b/problem.yaml
new file mode 100644
index 000000000..ab4596f68
--- /dev/null
+++ b/samps/contests/pointscoring/config/b/problem.yaml
@@ -0,0 +1,12 @@
+# Point Scoring problem configuration, problem 'b', version 1.0 (a duplicate of problem 'a')
+---
+
+name: b
+type: scoring
+
+limits:
+ timeout: 1
+
+input:
+ readFromSTDIN: true
+
diff --git a/samps/contests/pointscoring/config/b/problem_statement/problem.tex b/samps/contests/pointscoring/config/b/problem_statement/problem.tex
new file mode 100644
index 000000000..bb7e4cfc7
--- /dev/null
+++ b/samps/contests/pointscoring/config/b/problem_statement/problem.tex
@@ -0,0 +1,7 @@
+\problemtitle{Sumit b}
+
+\begin{document
+\section{Problem Statement}
+Write a program which reads a series of integers from stdin and prints a message "The sum of the integers is XXX", where XXX is the sum of the positive
+integers in the input.
+\end{document}
\ No newline at end of file
diff --git a/samps/contests/pointscoring/config/b/submissions/accepted/ISumit.java b/samps/contests/pointscoring/config/b/submissions/accepted/ISumit.java
new file mode 100644
index 000000000..38447a7a6
--- /dev/null
+++ b/samps/contests/pointscoring/config/b/submissions/accepted/ISumit.java
@@ -0,0 +1,29 @@
+
+// Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau.
+import java.io.*;
+
+//
+// File: ISumit.java
+// Purpose: to print the sum of the positive integers from stdin
+
+public class ISumit {
+ public static void main(String[] args) {
+ try {
+ BufferedReader br = new BufferedReader(new InputStreamReader(System.in), 1);
+
+ String line;
+ int sum = 0;
+ int rv = 0;
+ while ((line = br.readLine()) != null) {
+ rv = new Integer(line.trim()).intValue();
+ if (rv > 0)
+ sum = sum + rv;
+ }
+ System.out.print("The sum of the integers is ");
+ System.out.println(sum);
+ } catch (Exception e) {
+ System.out.println("Possible trouble reading stdin");
+ System.out.println("Message: " + e.getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/samps/contests/pointscoring/config/b/submissions/partially_accepted/ISumitPartial.java b/samps/contests/pointscoring/config/b/submissions/partially_accepted/ISumitPartial.java
new file mode 100644
index 000000000..1574be0a7
--- /dev/null
+++ b/samps/contests/pointscoring/config/b/submissions/partially_accepted/ISumitPartial.java
@@ -0,0 +1,28 @@
+
+// Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau.
+import java.io.*;
+
+//
+// File: ISumitPartial.java
+// Purpose: to print the sum of the integers from stdin but failing to ignore negative numbers
+
+public class ISumitPartial {
+ public static void main(String[] args) {
+ try {
+ BufferedReader br = new BufferedReader(new InputStreamReader(System.in), 1);
+
+ String line;
+ int sum = 0;
+ int rv = 0;
+ while ((line = br.readLine()) != null) {
+ rv = new Integer(line.trim()).intValue();
+ sum = sum + rv;
+ }
+ System.out.print("The sum of the integers is ");
+ System.out.println(sum);
+ } catch (Exception e) {
+ System.out.println("Possible trouble reading stdin");
+ System.out.println("Message: " + e.getMessage());
+ }
+ }
+}
diff --git a/samps/contests/pointscoring/config/b/submissions/wrong_answer/ISumitWA.java b/samps/contests/pointscoring/config/b/submissions/wrong_answer/ISumitWA.java
new file mode 100644
index 000000000..3c6030fe4
--- /dev/null
+++ b/samps/contests/pointscoring/config/b/submissions/wrong_answer/ISumitWA.java
@@ -0,0 +1,30 @@
+
+// Copyright (C) 1989-2019 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau.
+import java.io.*;
+
+//
+// File: ISumitWA.java
+// Purpose: to sum the integers from stdin but print an incorrect sum
+// (that is, to fail to correctly print the sum of the positive integers from stdin)
+
+public class ISumitWA {
+ public static void main(String[] args) {
+ try {
+ BufferedReader br = new BufferedReader(new InputStreamReader(System.in), 1);
+
+ String line;
+ int sum = 0;
+ int rv = 0;
+ while ((line = br.readLine()) != null) {
+ rv = new Integer(line.trim()).intValue();
+ if (rv > 0)
+ sum = sum + rv;
+ }
+ System.out.print("Dumn integers is ");
+ System.out.println(sum + 1024);
+ } catch (Exception e) {
+ System.out.println("Possible trouble reading stdin");
+ System.out.println("Message: " + e.getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/samps/contests/pointscoring/config/c/data/sample/01.ans b/samps/contests/pointscoring/config/c/data/sample/01.ans
new file mode 100644
index 000000000..15f395758
--- /dev/null
+++ b/samps/contests/pointscoring/config/c/data/sample/01.ans
@@ -0,0 +1 @@
+The sum of the integers is 75
diff --git a/samps/contests/pointscoring/config/c/data/sample/01.in b/samps/contests/pointscoring/config/c/data/sample/01.in
new file mode 100644
index 000000000..ad7b842a9
--- /dev/null
+++ b/samps/contests/pointscoring/config/c/data/sample/01.in
@@ -0,0 +1,3 @@
+25
+50
+0
diff --git a/samps/contests/pointscoring/config/c/data/secret/02.ans b/samps/contests/pointscoring/config/c/data/secret/02.ans
new file mode 100644
index 000000000..15f395758
--- /dev/null
+++ b/samps/contests/pointscoring/config/c/data/secret/02.ans
@@ -0,0 +1 @@
+The sum of the integers is 75
diff --git a/samps/contests/pointscoring/config/c/data/secret/02.in b/samps/contests/pointscoring/config/c/data/secret/02.in
new file mode 100644
index 000000000..f5669d925
--- /dev/null
+++ b/samps/contests/pointscoring/config/c/data/secret/02.in
@@ -0,0 +1,4 @@
+25
+50
+-25
+0
diff --git a/samps/contests/pointscoring/config/c/data/secret/03.ans b/samps/contests/pointscoring/config/c/data/secret/03.ans
new file mode 100644
index 000000000..1f1acd032
--- /dev/null
+++ b/samps/contests/pointscoring/config/c/data/secret/03.ans
@@ -0,0 +1 @@
+The sum of the integers is 50
diff --git a/samps/contests/pointscoring/config/c/data/secret/03.in b/samps/contests/pointscoring/config/c/data/secret/03.in
new file mode 100644
index 000000000..29643f4ed
--- /dev/null
+++ b/samps/contests/pointscoring/config/c/data/secret/03.in
@@ -0,0 +1,4 @@
+25
+25
+-25
+0
diff --git a/samps/contests/pointscoring/config/c/problem.yaml b/samps/contests/pointscoring/config/c/problem.yaml
new file mode 100644
index 000000000..bcf06400a
--- /dev/null
+++ b/samps/contests/pointscoring/config/c/problem.yaml
@@ -0,0 +1,12 @@
+# Point Scoring problem configuration, problem 'c', version 1.0 (currently a duplicat of problem 'a')
+---
+
+name: c
+type: scoring
+
+limits:
+ timeout: 1
+
+input:
+ readFromSTDIN: true
+
diff --git a/samps/contests/pointscoring/config/c/problem_statement/problem.tex b/samps/contests/pointscoring/config/c/problem_statement/problem.tex
new file mode 100644
index 000000000..3b8088aab
--- /dev/null
+++ b/samps/contests/pointscoring/config/c/problem_statement/problem.tex
@@ -0,0 +1,7 @@
+\problemtitle{Sumit c}
+
+\begin{document
+\section{Problem Statement}
+Write a program which reads a series of integers from stdin and prints a message "The sum of the integers is XXX", where XXX is the sum of the positive
+integers in the input.
+\end{document}
\ No newline at end of file
diff --git a/samps/contests/pointscoring/config/c/submissions/accepted/ISumit.java b/samps/contests/pointscoring/config/c/submissions/accepted/ISumit.java
new file mode 100644
index 000000000..38447a7a6
--- /dev/null
+++ b/samps/contests/pointscoring/config/c/submissions/accepted/ISumit.java
@@ -0,0 +1,29 @@
+
+// Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau.
+import java.io.*;
+
+//
+// File: ISumit.java
+// Purpose: to print the sum of the positive integers from stdin
+
+public class ISumit {
+ public static void main(String[] args) {
+ try {
+ BufferedReader br = new BufferedReader(new InputStreamReader(System.in), 1);
+
+ String line;
+ int sum = 0;
+ int rv = 0;
+ while ((line = br.readLine()) != null) {
+ rv = new Integer(line.trim()).intValue();
+ if (rv > 0)
+ sum = sum + rv;
+ }
+ System.out.print("The sum of the integers is ");
+ System.out.println(sum);
+ } catch (Exception e) {
+ System.out.println("Possible trouble reading stdin");
+ System.out.println("Message: " + e.getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/samps/contests/pointscoring/config/c/submissions/partially_accepted/ISumitPartial.java b/samps/contests/pointscoring/config/c/submissions/partially_accepted/ISumitPartial.java
new file mode 100644
index 000000000..1574be0a7
--- /dev/null
+++ b/samps/contests/pointscoring/config/c/submissions/partially_accepted/ISumitPartial.java
@@ -0,0 +1,28 @@
+
+// Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau.
+import java.io.*;
+
+//
+// File: ISumitPartial.java
+// Purpose: to print the sum of the integers from stdin but failing to ignore negative numbers
+
+public class ISumitPartial {
+ public static void main(String[] args) {
+ try {
+ BufferedReader br = new BufferedReader(new InputStreamReader(System.in), 1);
+
+ String line;
+ int sum = 0;
+ int rv = 0;
+ while ((line = br.readLine()) != null) {
+ rv = new Integer(line.trim()).intValue();
+ sum = sum + rv;
+ }
+ System.out.print("The sum of the integers is ");
+ System.out.println(sum);
+ } catch (Exception e) {
+ System.out.println("Possible trouble reading stdin");
+ System.out.println("Message: " + e.getMessage());
+ }
+ }
+}
diff --git a/samps/contests/pointscoring/config/c/submissions/wrong_answer/ISumitWA.java b/samps/contests/pointscoring/config/c/submissions/wrong_answer/ISumitWA.java
new file mode 100644
index 000000000..3c6030fe4
--- /dev/null
+++ b/samps/contests/pointscoring/config/c/submissions/wrong_answer/ISumitWA.java
@@ -0,0 +1,30 @@
+
+// Copyright (C) 1989-2019 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau.
+import java.io.*;
+
+//
+// File: ISumitWA.java
+// Purpose: to sum the integers from stdin but print an incorrect sum
+// (that is, to fail to correctly print the sum of the positive integers from stdin)
+
+public class ISumitWA {
+ public static void main(String[] args) {
+ try {
+ BufferedReader br = new BufferedReader(new InputStreamReader(System.in), 1);
+
+ String line;
+ int sum = 0;
+ int rv = 0;
+ while ((line = br.readLine()) != null) {
+ rv = new Integer(line.trim()).intValue();
+ if (rv > 0)
+ sum = sum + rv;
+ }
+ System.out.print("Dumn integers is ");
+ System.out.println(sum + 1024);
+ } catch (Exception e) {
+ System.out.println("Possible trouble reading stdin");
+ System.out.println("Message: " + e.getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/samps/contests/pointscoring/config/contest.yaml b/samps/contests/pointscoring/config/contest.yaml
new file mode 100644
index 000000000..c7c1ba5d6
--- /dev/null
+++ b/samps/contests/pointscoring/config/contest.yaml
@@ -0,0 +1,114 @@
+#
+#
+---
+
+name: Point Scoring Contest
+short-name: pointscoring
+
+scoreboard_type: score
+
+team-scoreboard-display-format-string : 'Team{:clientnumber} : {:teamname}'
+
+duration: 5:00:00
+
+# freeze time before end of contest
+scoreboard-freeze-length: 01:00:00
+
+# pc2 specific settings
+# elapsed: 0:00:00
+# remaining: 5:00:00
+running: true
+
+# default stop of first fail
+stop-on-first-failed-test-case: false
+
+# Halt contest clock at end of contest
+auto-stop-clock-at-end: true
+
+# Do not halt contest clock at end of contest, default setting
+# auto-stop-clock-at-end: false
+
+
+# default pc2 judging types
+computer-judged: true
+manual-review: false
+send-prelim-judgement: false
+
+# pc2 Shadow CCS settings
+# enable shadow mode
+shadow-mode: true
+# base URL for primary CCS REST service
+ccs-url: https://localhost:50443/contest
+# primary CCS REST login
+ccs-login: admin
+# primary CCS REST password
+ccs-password: admin
+
+languages:
+ - name: 'Java'
+ clics-id: 'java'
+
+ - name: 'python'
+ clics-id: 'pythonCLICSId'
+
+accounts:
+ - account: JUDGE
+ site: 1
+ count: 12
+
+ - account: SCOREBOARD
+ site: 1
+ count: 2
+
+ - account: FEEDER
+ site: 1
+ count: 4
+
+ - account: ADMINISTRATOR
+ site: 1
+ count: 3
+
+# set feeder1 as proxy
+team-proxy-accounts:
+ - account: FEEDER
+ number: 1
+
+
+auto-judging:
+ - account: JUDGE
+ site: 1
+ number: 6,7,8,9
+ letters: All
+ enabled: yes
+
+# General forms
+# permissions:
+# - account: TYPE
+# number: 10,11,12,13,14,15
+# number: All
+# enable: list of permissions
+# disable: list of permissions
+#
+#
+
+# disable Contest Security Alert window
+
+# account permissions
+permissions:
+
+ - account: ADMINISTRATOR
+ number: 1,2,3
+ disable: VIEW_SECURITY_ALERTS
+
+ - account: JUDGE
+ number: All
+ enable: GIVE_RUN
+
+
+# enable EDIT_RUN for FEEDER 3
+
+ - account: FEEDER
+ number: 3
+ enable: SHADOW_PROXY_TEAM, EDIT_RUN
+
+# EOF Contest Configuration
diff --git a/samps/contests/pointscoring/config/problemset.yaml b/samps/contests/pointscoring/config/problemset.yaml
new file mode 100644
index 000000000..e218bb309
--- /dev/null
+++ b/samps/contests/pointscoring/config/problemset.yaml
@@ -0,0 +1,19 @@
+problems:
+ - letter: A
+ short-name: a
+ clics-id : 'A-problem'
+ color: Dark green
+ rgb: '#399956'
+
+ - letter: B
+ short-name: b
+ clics-id : 'B-problem'
+ color: Purple
+ rgb: '#825eaa'
+
+ - letter: C
+ short-name: c
+ clics-id : 'C-problem'
+ color: Red
+ rgb: '#df1328'
+
diff --git a/src/edu/csus/ecs/pc2/clics/API202306/CLICSContestInfo.java b/src/edu/csus/ecs/pc2/clics/API202306/CLICSContestInfo.java
index 0d174bb1e..ba9b00ee9 100644
--- a/src/edu/csus/ecs/pc2/clics/API202306/CLICSContestInfo.java
+++ b/src/edu/csus/ecs/pc2/clics/API202306/CLICSContestInfo.java
@@ -107,7 +107,7 @@ public CLICSContestInfo(IInternalContest model, ContestInformation ci) {
}
}
penalty_time = Integer.valueOf(ci.getScoringProperties().getProperty(DefaultScoringAlgorithm.POINTS_PER_NO, "20"));
- scoreboard_type = "pass-fail";
+ scoreboard_type = ci.getScoreboardType().toString().toLowerCase();
}
public String toJSON() {
diff --git a/src/edu/csus/ecs/pc2/clics/API202306/CLICSJudgement.java b/src/edu/csus/ecs/pc2/clics/API202306/CLICSJudgement.java
index 98515855d..4271365ef 100644
--- a/src/edu/csus/ecs/pc2/clics/API202306/CLICSJudgement.java
+++ b/src/edu/csus/ecs/pc2/clics/API202306/CLICSJudgement.java
@@ -6,7 +6,6 @@
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;
@@ -26,7 +25,6 @@
*
*/
@JsonInclude(JsonInclude.Include.NON_NULL)
-@JsonFilter("rtFilter")
public class CLICSJudgement {
@JsonProperty
@@ -38,9 +36,8 @@ public class CLICSJudgement {
@JsonProperty
private String judgement_type_id;
-// Only for "score" type contests, N/A for ICPC pass/fail
-// @JsonProperty
-// private String score;
+ @JsonProperty
+ private Double score;
@JsonProperty
private String start_time;
diff --git a/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblem.java b/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblem.java
index cb1ca8fb0..203ceca57 100644
--- a/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblem.java
+++ b/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblem.java
@@ -8,7 +8,9 @@
import edu.csus.ecs.pc2.core.StringUtilities;
import edu.csus.ecs.pc2.core.model.IInternalContest;
import edu.csus.ecs.pc2.core.model.Problem;
+import edu.csus.ecs.pc2.core.model.ProblemDataFiles;
import edu.csus.ecs.pc2.core.util.IJSONTool;
+import edu.csus.ecs.pc2.imports.ccs.TestDataGroup;
import edu.csus.ecs.pc2.services.core.JSONUtilities;
/**
@@ -48,9 +50,8 @@ public class CLICSProblem {
@JsonProperty
private int test_data_count;
-// only for "score" type contests, N/A for pc2/wf pass-fail type contests
-// @JsonProperty
-// private int max_score;
+ @JsonProperty
+ private Double max_score;
// The next two will be 'null' for now until we implement the new json CPF
@JsonProperty("package")
@@ -59,6 +60,8 @@ public class CLICSProblem {
@JsonProperty
private CLICSFileReference [] statement;
+ private boolean isPointScoring = false;
+
/**
* Fill in properties for a Problem description.
*
@@ -82,15 +85,25 @@ public CLICSProblem(IInternalContest model, Problem problem, int ordinal) {
}
test_data_count = problem.getNumberTestCases();
time_limit = problem.getTimeOutInSeconds();
+ if(model.getContestInformation().isScoreboardTypeScore()) {
+ isPointScoring = true;
+ ProblemDataFiles problemDataFiles = model.getProblemDataFile(problem);
+ if(problemDataFiles != null) {
+ TestDataGroup [] testDataGroups = problemDataFiles.getJudgesDataGroups();
+ // Really, there's only 1 top level TestDataGroup
+ if(testDataGroups != null && testDataGroups.length > 0) {
+ max_score = testDataGroups[0].getRangeMax();
+ }
+ }
+ }
}
public String toJSON() {
-
try {
ObjectMapper mapper = JSONUtilities.getObjectMapper();
return mapper.writeValueAsString(this);
} catch (Exception e) {
- return "Error creating JSON for CLICS problem info " + e.getMessage();
+ return "Error creating JSON for CLICSProblem " + e.getMessage();
}
}
}
diff --git a/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblemScore.java b/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblemScore.java
index 1cab9dedb..c6e4c08c1 100644
--- a/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblemScore.java
+++ b/src/edu/csus/ecs/pc2/clics/API202306/CLICSProblemScore.java
@@ -33,9 +33,8 @@ public class CLICSProblemScore {
@JsonProperty
private boolean solved;
-// Not needed for pass-fail contest
-// @JsonProperty
-// private double score;
+ @JsonProperty
+ private Double score;
@JsonProperty
private int time;
diff --git a/src/edu/csus/ecs/pc2/clics/API202306/CLICSScore.java b/src/edu/csus/ecs/pc2/clics/API202306/CLICSScore.java
index a9b3b8c40..40a36e24d 100644
--- a/src/edu/csus/ecs/pc2/clics/API202306/CLICSScore.java
+++ b/src/edu/csus/ecs/pc2/clics/API202306/CLICSScore.java
@@ -23,9 +23,8 @@ public class CLICSScore {
@JsonProperty
private int total_time;
-// Not used for pass/fail contest and we don't want it appearing in the json
-// @JsonProperty
-// private int score;
+ @JsonProperty
+ private Double score;
@JsonProperty
private int time;
@@ -50,9 +49,11 @@ public CLICSScore(TeamStanding teamStanding) {
if(num_solved > 0) {
// Problem solution time is in minutes.
time = Integer.parseInt(teamStanding.getLastSolved());
+ score = Double.parseDouble(teamStanding.getScore());
}
}
+
public int getNum_solved() {
return num_solved;
}
@@ -65,4 +66,18 @@ public int getTime() {
return time;
}
+ /**
+ * @return the score
+ */
+ public double getScore() {
+ return score.doubleValue();
+ }
+
+ /**
+ * @param score the score to set
+ */
+ public void setScore(double score) {
+ this.score = score;
+ }
+
}
diff --git a/src/edu/csus/ecs/pc2/core/Utilities.java b/src/edu/csus/ecs/pc2/core/Utilities.java
index 81c1aae88..5b9d94c7e 100644
--- a/src/edu/csus/ecs/pc2/core/Utilities.java
+++ b/src/edu/csus/ecs/pc2/core/Utilities.java
@@ -1,4 +1,4 @@
-// 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.core;
import java.io.BufferedReader;
@@ -57,7 +57,7 @@
public final class Utilities {
private static boolean debugMode = false;
-
+
private static boolean showStandingsPanes = true;
/**
@@ -81,12 +81,12 @@ public final class Utilities {
/**
* CLICS path where judge's (secret) data file are stored
- *
+ *
* @see #getSecretDataPath(String, Problem)
* @see #getSecretDataPath(String, String)
*/
public static final String SECRET_DATA_PATH = "data" + File.separator + ExportYAML.SECRET_DIRECTORY_NAME;
-
+
/**
* CLICS directory where judge's sample data file are stored
*/
@@ -120,7 +120,7 @@ public final class Utilities {
/**
* File Types.
- *
+ *
*/
public enum DataFileType {
/**
@@ -164,7 +164,7 @@ public static String getSecretDataPath(String baseCDPPath, String problemShortNa
public static String getSecretDataPath(String baseCDPPath, Problem problem) {
return getSecretDataPath(baseCDPPath, problem.getShortName());
}
-
+
/**
* Return CLICS path for sample data and answer file names.
*/
@@ -257,7 +257,7 @@ public static boolean isIntegerNumber(String s) {
/**
* Compares 2 char arrays for equality.
- *
+ *
* @param oldBuffer
* @param newBuffer
* @return true if oldBuffer is the same size and has the same contents of newBuffer
@@ -283,7 +283,7 @@ public static boolean isEquals(char[] oldBuffer, char[] newBuffer) {
/**
* Returns lines from file.
- *
+ *
* @param filename
* String file to load
* @return lines from file
@@ -322,11 +322,11 @@ public static String[] loadFile(String filename) throws IOException {
return out;
}
-
+
/**
* Load string array with file contents.
- *
+ *
* @param filename
* @param maximumLinesReturned minimum value is 1.
* @return lines from file
@@ -341,9 +341,9 @@ public static String[] loadFile(String filename, long maximumLinesReturned) thro
if (new File(filename).exists()) {
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(filename), "UTF8"));
-
+
long lineCount = 0;
-
+
String line = in.readLine();
while (lineCount < maximumLinesReturned && line != null) {
lines.add(line);
@@ -354,12 +354,12 @@ public static String[] loadFile(String filename, long maximumLinesReturned) thro
in = null;
}
- return (String[]) lines.toArray(new String[lines.size()]);
+ return lines.toArray(new String[lines.size()]);
}
/**
* Get Current Working Directory.
- *
+ *
* @return current working directory.
*/
public static String getCurrentDirectory() {
@@ -399,7 +399,7 @@ public static String getL10nDateTime(int dateStyle, int timeStyle, Locale curren
/**
* Returns a date-time String as defined by RFC 2822 section 3.3 Useful for mail messages.
- *
+ *
* @return the date in rfc2822 format
*/
public static String getRFC2822DateTime() {
@@ -436,7 +436,7 @@ public static String getRFC2822DateTime() {
/**
* Returns Yes if true, No if false.
- *
+ *
* @param b
* @return Yes or No
*/
@@ -458,11 +458,11 @@ public static String trueFalseString(boolean value, String trueString, String fa
/**
* Load INI file.
- *
+ *
* This will read a text file and strip out blank/empty lines and lines that start with a hash mark.
*
* This will also trim the input lines.
- *
+ *
* @param filename
* file to be read
* @return String [] null if can't read/find file, else lines infile
@@ -490,7 +490,7 @@ public static String[] loadINIFile(String filename) {
return null;
}
- return (String[]) v.toArray(new String[v.size()]);
+ return v.toArray(new String[v.size()]);
}
public static String forHTML(String aText) {
@@ -606,10 +606,10 @@ public static String getReportFilename(IReport selectedReport) {
}
-
+
/**
- * Create/Write a report to file in {@value Constants#REPORT_DIRECTORY_NAME}
- *
+ * Create/Write a report to file in {@value Constants#REPORT_DIRECTORY_NAME}
+ *
* @param report
* IReport to write
* @param contest
@@ -620,11 +620,11 @@ public static String getReportFilename(IReport selectedReport) {
public static String createReport(IReport report, IInternalContest contest, IInternalController controller, boolean printHeaderAndFooter) throws FileNotFoundException {
return createReport(Constants.REPORT_DIRECTORY_NAME, report, contest, controller, printHeaderAndFooter);
}
-
+
/**
- * Create/Write a report to file in outputDirectoryName, creates outputDirectoryName if dir doesnot exist.
- *
- *
+ * Create/Write a report to file in outputDirectoryName, creates outputDirectoryName if dir doesnot exist.
+ *
+ *
* @param outputDirectoryName output directory name
* @param report
* IReport to write
@@ -636,7 +636,7 @@ public static String createReport(IReport report, IInternalContest contest, IInt
public static String createReport(String outputDirectoryName, IReport report, IInternalContest contest, IInternalController controller, boolean printHeaderAndFooter) throws FileNotFoundException {
String filename = getReportFilename(report);
-
+
if (outputDirectoryName != null && outputDirectoryName.trim().length() > 0) {
ExecuteUtilities.ensureDirectory(outputDirectoryName);
filename = outputDirectoryName + File.separator + filename;
@@ -712,7 +712,7 @@ private static void printReportHeader(PrintWriter printWriter, IReport report, I
/**
* Create and view report.
- *
+ *
* @param report
* @param title
* title for tab in pane for this report
@@ -722,7 +722,7 @@ private static void printReportHeader(PrintWriter printWriter, IReport report, I
public static void viewReport(IReport report, String title, IInternalContest contest, IInternalController controller, boolean printHeaderAndFooter) {
try {
-
+
String filename = createReport(report, contest, controller, printHeaderAndFooter);
MultipleFileViewer multipleFileViewer = new MultipleFileViewer(controller.getLog());
@@ -741,7 +741,7 @@ public static void viewReport(IReport report, String title, IInternalContest con
/**
* Dump String array.
- *
+ *
* @param out
* stream to output to
* @param prefix
@@ -781,7 +781,7 @@ public static void debugPrint(Exception e) {
/**
* View report
- *
+ *
* @see #viewReport(IReport, String, IInternalContest, IInternalController, boolean)
* @param report
* @param title
@@ -794,9 +794,9 @@ public static void viewReport(IReport report, String title, IInternalContest con
/**
* Create disk file for input SerializedFile.
- *
+ *
* Returns true if file is written to disk and is not null.
- *
+ *
* @param file
* @param outputFileName
* @return true if file written to disk.
@@ -837,7 +837,7 @@ public static StringBuffer join(String delimiter, String[] strings) {
/**
* return full path for input relative path/filename.
- *
+ *
* @param relativePath
*/
public static String getFullPath(String relativePath) {
@@ -851,7 +851,7 @@ public static String getFullPath(String relativePath) {
/**
* Convert String to second.
- *
+ *
* @param s
* string in form hh:mm:ss, ss or mm:ss
* @return -1 if invalid time string, else returns number of seconds
@@ -909,7 +909,7 @@ public static long convertStringToSeconds(String s) {
/**
* Parse and return positive long.
- *
+ *
* @param s1
* @return -1 if non-long string, else long value
*/
@@ -926,7 +926,7 @@ public static long stringToLong(String s1) {
/**
* return date/time string.
- *
+ *
* @return curernt date using format {@value Utilities#DATE_TIME_FORMAT_STRING}.
*/
public static String getDateTime() {
@@ -935,7 +935,7 @@ public static String getDateTime() {
/**
* Convert String to second. Expects input in form: ss or mm:ss or hh:mm:ss
- *
+ *
* @param s
* string to be converted to seconds
* @return -1 if invalid time string, 0 or >0 if valid
@@ -993,7 +993,7 @@ public long stringToLongSecs(String s) {
/**
* Locate judges data file on disk.
- *
+ *
* @param problem
* @param serializedFile
* @param alternateCDPPath
@@ -1003,11 +1003,11 @@ public long stringToLongSecs(String s) {
public static String locateJudgesDataFile(Problem problem, SerializedFile serializedFile, String alternateCDPPath, DataFileType judgeDataFile) {
String testFileName = locateJudgesDataFile(problem, serializedFile.getName(), alternateCDPPath);
-
+
if (testFileName != null && fileExists(testFileName)) {
return testFileName;
}
-
+
testFileName = serializedFile.getAbsolutePath();
if (fileExists(testFileName)) {
@@ -1016,13 +1016,13 @@ public static String locateJudgesDataFile(Problem problem, SerializedFile serial
return null;
}
-
+
public static String locateJudgesDataFile(Problem problem, String baseFileName, String alternateCDPPath) {
String testFileName = null;
if (alternateCDPPath != null && alternateCDPPath.trim().length() > 0) {
-
+
// Try to find data file under secret directory
testFileName = getSecretDataPath(alternateCDPPath, problem) + File.separator + baseFileName;
@@ -1030,9 +1030,9 @@ public static String locateJudgesDataFile(Problem problem, String baseFileName,
return testFileName;
}
}
-
+
if (alternateCDPPath != null && alternateCDPPath.trim().length() > 0) {
-
+
// Try to find data file under sample directory
testFileName = getSampleDataPath(alternateCDPPath, problem) + File.separator + baseFileName;
@@ -1062,7 +1062,7 @@ public static boolean fileExists(String filename) {
/**
* Write file to output printWriter.
- *
+ *
* @param printWriter
* @param outputfilename
*/
@@ -1119,7 +1119,7 @@ public static String[] fullJudgesAnswerFilenames(IInternalContest contest, Probl
* For external files (aka CDP files) on the JUDGE the path is from {@link ContestInformation#getJudgeCDPBasePath()} where ContestInformation is in {@link IInternalContest#getContestInformation()}
* .
* For external files (aka CDP files) on the ADMIN (or JUDGE without the CDP path set) the path is stored in the {@link Problem#getExternalDataFileLocation()}
- *
+ *
* @param contest
* @param problem
* @param serializedFiles
@@ -1132,7 +1132,7 @@ public static String[] getProblemfullFilenames(IInternalContest contest, Problem
ArrayList output = new ArrayList();
String originalJudgeDataPath = getJudgeCDPLocation(contest);
-
+
if (problem.isUsingExternalDataFiles()) {
ClientId id = contest.getClientId();
if (id == null) {
@@ -1144,7 +1144,7 @@ public static String[] getProblemfullFilenames(IInternalContest contest, Problem
if (!"".equals(judgeDataFilesPath)) {
judgeDataFilesPath = Utilities.getSecretDataPath(judgeDataFilesPath, problem) + File.separator;
File judgeDir = new File(judgeDataFilesPath);
-
+
if (!judgeDir.isDirectory()) {
judgeDataFilesPath = judgeDataFilesPath.replaceFirst(".data.secret", "");
}
@@ -1186,12 +1186,12 @@ public static String[] getProblemfullFilenames(IInternalContest contest, Problem
}
}
}
- return (String[]) output.toArray(new String[output.size()]);
+ return output.toArray(new String[output.size()]);
}
/**
* Start Windows Explorer.
- *
+ *
* @param dir
* directory to display
* @throws IOException
@@ -1215,14 +1215,14 @@ public static void startExplorer(String directoryName) {
/**
* Dump contents of ProblemDataFiles using ProblemReport
- *
+ *
*
* PrintWriter printWriter = new PrintWriter(System.out);
* dump(problemDataFiles, "dump this");
* printWriter.close();
* printWriter = null;
*
- *
+ *
* @param printWriter
* @param dataFiles
* @param message
@@ -1244,7 +1244,7 @@ public static void dump(PrintWriter printWriter, ProblemDataFiles dataFiles, Str
/**
* Dump problem data files to System.out.
- *
+ *
* @param dataFiles
* @param message
*/
@@ -1256,7 +1256,7 @@ public static void dump(ProblemDataFiles dataFiles, String message) {
/**
* Return list of directory entries matching with extension.
- *
+ *
* @param directoryName
* @param extension
* @return list of basename filenames in directory directoryName
@@ -1279,12 +1279,12 @@ public static String[] getFileNames(String directoryName, String extension) {
}
}
- return (String[]) list.toArray(new String[list.size()]);
+ return list.toArray(new String[list.size()]);
}
/**
* If there is a freezeTime set, return the contestLength - freezeTime, else the contestLength.
- *
+ *
* @param model
* - contest to get info from
* @return The freeze time in seconds (counting up from contest Start)
@@ -1311,14 +1311,14 @@ public static SerializedFile[] createSerializedFiles(String dataFileBaseDirector
outfiles.add(new SerializedFile(filename, externalFilesFlag));
}
- return (SerializedFile[]) outfiles.toArray(new SerializedFile[outfiles.size()]);
+ return outfiles.toArray(new SerializedFile[outfiles.size()]);
}
/**
* Find base data path for problems.
- *
+ *
* If {@value #SECRET_DATA_DIR} in filePath will strop off anything including and after {@value #SECRET_DATA_DIR}.
- *
+ *
* @param filePath
* @return directory name up to {@value #SECRET_DATA_DIR}
*/
@@ -1330,8 +1330,8 @@ public static String findDataBasePath(String filePath) {
}
return filePath;
}
-
-
+
+
/**
* If file has extension replaces with replacement.
* @param fullName
@@ -1345,22 +1345,22 @@ public static String replaceExtension(String fullName, String extension, String
/**
* Validate problem files.
- *
+ *
* Only checks for files on disk if {@link Problem#isUsingExternalDataFiles()} is true.
- *
+ *
* Will throw a MultipleIssuesException if any directories or files are missing, use {@link MultipleIssuesException#getIssueList()) for list of missing files or errors.
- *
+ *
* @param contest
* @param cdpPath
* - base path for CDP config directory
* @param problem
* problem to validate
- * @param allProblemDCPFiles
+ * @param allProblemCDPFiles
* include problem.tex and problem.yaml files.
* @return true if all files present
* @throws MultipleIssuesException
*/
- public static boolean validateCDP(IInternalContest contest, String cdpPath, Problem problem, boolean allProblemDCPFiles) throws MultipleIssuesException {
+ public static boolean validateCDP(IInternalContest contest, String cdpPath, Problem problem, boolean allProblemCDPFiles) throws MultipleIssuesException {
List messages = new ArrayList<>();
if (problem == null) {
@@ -1399,44 +1399,22 @@ public static boolean validateCDP(IInternalContest contest, String cdpPath, Prob
// If no secret directory - done, show Missing data directory message
messages.add(problemTitle + "\tMissing data directory, expected at: " + dataPath + " or (" + dataPath + File.separator + "data" + File.separator + "secret)");
} else {
-
- int missingData = 0;
- int missingAnswer = 0;
-
- for (int i = 0; i < problem.getNumberTestCases(); i++) {
-
- String dataFile = problem.getDataFileName(i + 1);
- String ansFile = problem.getAnswerFileName(i + 1);
-
- String judgeFileName = dataPath + File.separator + dataFile;
- String answerFilename = dataPath + File.separator + ansFile;
-
- if (dataFile != null && !isFileThere(judgeFileName)) {
- // Try to find file under samples
- String testFile = locateJudgesDataFile(problem, dataFile, cdpPath);
- if (isFileThere(testFile)) {
- judgeFileName = testFile;
- answerFilename = replaceExtension(testFile, "in", "ans");
- }
- }
-
- if (dataFile != null && !isFileThere(judgeFileName)) {
- messages.add(problemTitle + "\tMissing judge file '" + dataFile + "' in " + dataPath);
- missingData++;
- }
-
- if (ansFile != null && !isFileThere(answerFilename)) {
- messages.add(problemTitle + "\tMissing answer file '" + ansFile + "' in " + dataPath);
- missingAnswer++;
- }
- }
- if ((missingData + missingAnswer) > 0) {
- messages.add(problemTitle + "\ttotal files missing = " + (missingData + missingAnswer));
+ ProblemDataFiles dataFiles = contest.getProblemDataFile(problem);
+ SerializedFile[] inFiles = dataFiles.getJudgesDataFiles();
+ SerializedFile[] ansFiles = dataFiles.getJudgesAnswerFiles();
+
+ checkSerializedFileList(inFiles, problemTitle, "input", messages);
+ checkSerializedFileList(ansFiles, problemTitle, "answer", messages);
+
+ int nTestCases = inFiles.length;
+ int nAnsCases = ansFiles.length;
+ if(inFiles.length != ansFiles.length) {
+ messages.add(problemTitle + "\tDifferent numbers of judge's input (" + nTestCases + ") and answer (" +
+ nAnsCases + ") test cases in " + problemDir);
}
}
-
- if (allProblemDCPFiles) {
+ if (allProblemCDPFiles) {
// check for problem.tex
String laTextProblemFilename = problemDir + IContestLoader.DEFAULT_PROBLEM_LATEX_FILENAME;
@@ -1462,11 +1440,35 @@ public static boolean validateCDP(IInternalContest contest, String cdpPath, Prob
}
}
+ /**
+ * Validates that all the files in the supplied list exist
+ *
+ * @param files the list of files
+ * @param problem name of problem for messages
+ * @param fileType the type of file, eg. input, answer - used for messages
+ * @param messages list of error messages to add to in the event of errors
+ *
+ * @return true of the list is OK, false if there were errors
+ */
+ private static boolean checkSerializedFileList(SerializedFile[] files, String problemTitle, String fileType, List messages) {
+ String file;
+ boolean result = true;
+
+ for(SerializedFile f : files) {
+ file = f.getAbsolutePath();
+ if(!isFileThere(file)) {
+ messages.add(problemTitle + "\tMissing judge " + fileType + " file '" + file + "'");
+ result = false;
+ }
+ }
+ return(false);
+ }
+
/**
* Validate all problem data files.
- *
+ *
* Only checks for files on disk if {@link Problem#isUsingExternalDataFiles()} is true.
- *
+ *
* @see #validateCDP(IInternalContest, String, Problem, boolean)
*/
public static boolean validateCDP(IInternalContest contest, String cdpPath) throws MultipleIssuesException {
@@ -1499,9 +1501,9 @@ public static boolean validateCDP(IInternalContest contest, String cdpPath) thro
/**
* For the input number, returns an upper-case letters.
- *
+ *
* 1 = A, 2 = B, ..., 27 = AA, ... 702 = ZZ
- *
+ *
* @param id
* problem number, base one (not zero), range 1 (A) through 702 (ZZ)
* @return uppercase letters
@@ -1525,9 +1527,9 @@ public static String getProblemLetter(int id) {
/**
* The problem number, base 1.
- *
+ *
* A = 1, B = 2, etc.
- *
+ *
* @param contest
* @param problem
* @return the problem number, base 1. Returns 0 if not found.
@@ -1545,9 +1547,9 @@ public static int getProblemNumber(IInternalContest contest, Problem problem) {
/**
* Return date/time string for now.
- *
+ *
* Uses format {@value #FORMAT_YYYY_MM_DD_HH_MM_SS}.
- *
+ *
* @return
*/
public static String getDateTimeString() {
@@ -1556,9 +1558,9 @@ public static String getDateTimeString() {
/**
* Convert DOS file seperator with unix.
- *
+ *
* replace all \ with /.
- *
+ *
* @param filename
* @return
*/
@@ -1568,7 +1570,7 @@ public static String unixifyPath(String filename) {
/**
* Write lines to file.
- *
+ *
* @param filename
* @param lines
* @throws FileNotFoundException
@@ -1584,18 +1586,18 @@ public static void writeLinesToFile(String filename, String[] lines) throws File
}
/**
- * Checks the specified {@link SerializedFile} for error messages and/or exceptions.
- * This method is provided because the SerializedFile class does not throw exceptions when errors occur
- * (such as its constructor encountering a "File Not Found" condition). Rather, the SerializedFile class simply
- * sets an "error message" and records the "exception" within the SerializedFile object.
- * This method throws any exception found in the specified SerializedFile, or returns true if there is an error message (but no exception).
+ * Checks the specified {@link SerializedFile} for error messages and/or exceptions.
+ * This method is provided because the SerializedFile class does not throw exceptions when errors occur
+ * (such as its constructor encountering a "File Not Found" condition). Rather, the SerializedFile class simply
+ * sets an "error message" and records the "exception" within the SerializedFile object.
+ * This method throws any exception found in the specified SerializedFile, or returns true if there is an error message (but no exception).
* It returns false if the specified SerializedFile contains no error message or is null.
- *
+ *
* @param serFile
* the SerializedFile to be checked
- *
+ *
* @return true if the SerializedFile contains an error message or exceptions; false if the SerializedFile contains no error message or is null
- *
+ *
* @throws Exception
* if any exception is found in the SerializedFile object
*/
@@ -1623,9 +1625,9 @@ public static boolean serializedFileError(SerializedFile serFile) throws Excepti
/**
* Return OS type.
- *
+ *
* Attempts to identify {@link OSType}.
- *
+ *
* @return {@link OSType#UNCLASSIFIED} if cannot be determined else returns {@link OSType}
*/
public static OSType getOSType() {
@@ -1669,9 +1671,9 @@ public static OSType getOSType() {
/**
* Return true if considered an executable.
- *
+ *
* If not extension found or extension in #VALID_PROGRAM_EXTENSIONS
- *
+ *
* @param baseFileName
* @return
*/
@@ -1693,7 +1695,7 @@ public static boolean isExecutableExtension(String baseFileName) {
/**
* Convert from int array to list.
- *
+ *
* @param proxySites
* @return
*/
@@ -1709,7 +1711,7 @@ public static List arrayToList(int[] proxySites) {
/**
* return base name, strip path and extension.
- *
+ *
* @param filename
* @return
*/
@@ -1728,7 +1730,7 @@ public static String getFileBaseName(String filename) {
return baseFileName;
}
-
+
/**
* Print stack trace with only elements with csus in them.
* @param printStream
@@ -1736,19 +1738,19 @@ public static String getFileBaseName(String filename) {
*/
public static void printStackTrace(PrintStream printStream, Exception e) {
printStackTrace(printStream, e, "csus");
-
+
}
/**
* Prints a stack trace, prints stack elements which only matches pattern.
- *
+ *
* Example to only print stack trace elements with csus:
- *
+ *
*
* Utilities.printStackTrace(System.err,e,"csus");
- *
+ *
* prints:
- *
+ *
* java.sql.SQLException: No value specified for parameter 7
* Matching: csus
* at edu.csus.ecs.pc2.db.adapters.MySqlDatabaseAdapter(MySqlDatabaseAdapter.java:274)
@@ -1756,7 +1758,7 @@ public static void printStackTrace(PrintStream printStream, Exception e) {
* at edu.csus.ecs.pc2.db.adapters.MySqlDatabaseAdapter(MySqlDatabaseAdapter.java:2442)
* at edu.csus.ecs.pc2.db.adapters.DatabaseAdapterTest(DatabaseAdapterTest.java:237)
*
- *
+ *
* @param printStream
* @param e
* @param pattern
@@ -1796,7 +1798,7 @@ public static void dumpProblemGroups(String message, Problem problem) {
/**
* If object is empty/null throw InvalidParameterException
- *
+ *
* @param obj
* @param message
*/
@@ -1808,7 +1810,7 @@ public static void isEmpty(Object obj, String message) {
/**
* If string is empty/null throw InvalidParameterException
- *
+ *
* @param s
* @param message
*/
@@ -1820,7 +1822,7 @@ public static void isEmptyString(String s, String message) {
/**
* Convert String to int.
- *
+ *
* @param string
* @param defaultNumber
* used if invalid or null string
@@ -1843,63 +1845,63 @@ public static int nullSafeToInt(String string, int defaultNumber) {
/**
* Converts a CLICS Contest API "RELTIME" (contest time) string to milliseconds.
- *
+ *
* The format for CLICS RELTIME values is: (-)?(h)*h:mm:ss(.uuu)?
* Note that this differs from standard ISO 8601 times in that it allows more than two hour
* digits and in that the optional fractional seconds (.uuu), if specified, MUST have exactly
* three digits (the ISO 8601 spec doesn't require/restrict the number of digits in the fraction).
- *
+ *
* @param time a time string in CLICS Contest API format
* @return the number of milliseconds corresponding to the specified time string, or the most
* negative long value possible (Long.MIN_VALUE) if the string could not be parsed correctly
*/
public static long convertCLICSContestTimeToMS(String time) {
-
+
final long MSECS_PER_HOUR = 1000 * 60 * 60 ;
final long MSECS_PER_MIN = 1000 * 60 ;
final long MSECS_PER_SEC = 1000 ;
-
+
boolean isNegative = false ;
-
+
long hoursMS;
long minsMS;
long secondsMS;
long msecs;
-
+
try {
//strip off any optional minus sign
if (time.startsWith("-")) {
time = new String(time.substring(1,time.length()));
isNegative = true;
}
-
+
String [] fields = time.split(":");
if (fields.length!=3) {
//missing one or more required fields
return Long.MIN_VALUE;
}
-
+
//verify there are the required number of chars in each field (>=1 for hours, =2 for mins, either 2 or 6 secs)
if (fields[0].length()<1 || fields[1].length()!=2 || (fields[2].length()!=2 && fields[2].length()!=6) ) {
return Long.MIN_VALUE;
}
-
+
//verify the minutes digits are legitmate time values
int mins = Integer.parseInt(fields[1]) ;
if (mins<0 || mins>59) {
return Long.MIN_VALUE;
}
-
+
//split out any option fractional part in the seconds field
String [] secondsFields = fields[2].split("\\.");
-
+
//verify the seconds digits are legitmate time values
int secs = Integer.parseInt(secondsFields[0]) ;
if (secs<0 || secs > 59) {
return Long.MIN_VALUE;
}
-
-
+
+
//process any optional fraction on the input
msecs = 0;
if (secondsFields.length>1) {
@@ -1917,9 +1919,9 @@ public static long convertCLICSContestTimeToMS(String time) {
hoursMS = Long.parseLong(fields[0]) * MSECS_PER_HOUR;
minsMS = Long.parseLong(fields[1]) * MSECS_PER_MIN;
secondsMS = Long.parseLong(secondsFields[0]) * MSECS_PER_SEC;
-
+
long retVal = hoursMS + minsMS + secondsMS + msecs ;
-
+
if (isNegative) {
retVal = -retVal ;
}
@@ -1931,10 +1933,10 @@ public static long convertCLICSContestTimeToMS(String time) {
return Long.MIN_VALUE;
}
}
-
+
/**
* Fetch directories names (full path).
- *
+ *
* @param directory
* @return list of directories (prepended by directory)
*/
@@ -1953,7 +1955,7 @@ public static List getDirectoryNames(String directory) {
for (String name : entries) {
String entry = directory + File.separator + name;
-
+
if (new File(entry).isDirectory()) {
list.add(entry);
}
@@ -1961,10 +1963,10 @@ public static List getDirectoryNames(String directory) {
return list;
}
-
+
/**
* Fetch all CDP data directories for all problems under cdpConfigDirectory directory.
- *
+ *
* @param cdpConfigDirectory
* @return a list of data directories (full path)
*/
@@ -1990,25 +1992,25 @@ public static List getCDPDataDirectories(String cdpConfigDirectory) {
return list;
- }
-
+ }
+
/**
* Accepts a "duration" (an amount of time) in milliseconds and returns that duration in formatted form.
* The general form of the returned format is HHHH:MM:SS.sss, where the number of hour digits will always be
- * at least two, but may be more depending on the input value
+ * at least two, but may be more depending on the input value
* (in other words, this is not a "time of day" which is restricted to just two hour-digits).
- * Note however that the minutes and seconds fields will always be two digits, and the msec field will
+ * Note however that the minutes and seconds fields will always be two digits, and the msec field will
* always be three digits.
* If the input value is negative then it is converted to a positive value and the returned value is
* the positive value preceded by a minus sign.
- *
+ *
* @param milliseconds the duration to be formatted, in msec.
* @return the formatted value of the input duration.
*/
public static String formatDuration (long milliseconds) {
-
+
String result = "";
-
+
//ensure calculations are based on positive milliseconds
long posMsec = milliseconds;
if (milliseconds<0) {
@@ -2018,8 +2020,8 @@ public static String formatDuration (long milliseconds) {
long msecPerSecond = Constants.MS_PER_SECOND;
long msecPerMinute = Constants.MS_PER_MINUTE;
- long msecPerHour = msecPerMinute*60 ;
-
+ long msecPerHour = msecPerMinute*60 ;
+
long hours = posMsec / msecPerHour ; //whole hours (fractional portion is truncated
if (hours<10) {
//prepend a zero to insure at least two hour digits
@@ -2027,21 +2029,21 @@ public static String formatDuration (long milliseconds) {
}
String hourString = Long.toString(hours);
result += hourString + ":";
-
+
long minutes = (posMsec - (hours*msecPerHour)) / (60*1000); //whole minutes (truncated)
if (minutes<10) {
result += "0";
}
String minuteString = Long.toString(minutes);
result += minuteString + ":";
-
+
long seconds = (posMsec - (hours*msecPerHour) - (minutes*msecPerMinute)) / 1000; //whole seconds (truncated)
if (seconds<10) {
result += "0";
}
String secondsString = Long.toString(seconds);
result += secondsString + ".";
-
+
long millis = posMsec - (hours*msecPerHour) - (minutes*msecPerMinute) - (seconds*msecPerSecond); //fractional seconds (truncated)
if (millis<100) {
result += "0";
@@ -2051,24 +2053,24 @@ public static String formatDuration (long milliseconds) {
}
String fractionString = Long.toString(millis);
result += fractionString;
-
+
return result;
}
public static void setShowStandingsPanes(boolean showStandingsPanes) {
Utilities.showStandingsPanes = showStandingsPanes;
}
-
+
public static boolean isShowStandingsPanes() {
return showStandingsPanes;
}
-
+
/**
* Concatenate Arrays.
- *
- * This is null-safe, arrays can be null and will return
+ *
+ * This is null-safe, arrays can be null and will return
* an array.
- *
+ *
* @param one first array
* @param two secodn array
* @return a new array which contains contents of one and two arrays
@@ -2090,14 +2092,14 @@ public static String[] concatenateArrays(String[] one, String[] two) {
return newArray;
}
-
-
+
+
/**
* Concatenate Arrays.
- *
- * This is null-safe, arrays can be null and will return
+ *
+ * This is null-safe, arrays can be null and will return
* an array.
- *
+ *
* @param one first array
* @param two secodn array
* @return a new array which contains contents of one and two arrays
diff --git a/src/edu/csus/ecs/pc2/core/model/ContestInformation.java b/src/edu/csus/ecs/pc2/core/model/ContestInformation.java
index b1a424c21..19a2343c0 100644
--- a/src/edu/csus/ecs/pc2/core/model/ContestInformation.java
+++ b/src/edu/csus/ecs/pc2/core/model/ContestInformation.java
@@ -141,6 +141,23 @@ public enum TeamDisplayMask {
ALIAS,
}
+ public enum ScoreboardType {
+ PASSFAIL("pass-fail"),
+ SCORE("score");
+
+ private final String type;
+
+ private ScoreboardType(String type) {
+ this.type = type;
+ }
+ public String getType() {
+ return type;
+ }
+
+ }
+
+ private ScoreboardType scoreboardType = ScoreboardType.PASSFAIL;
+
private Properties scoringProperties = new Properties();
/**
@@ -449,6 +466,37 @@ public boolean isSameAs(ContestInformation contestInformation) {
return false;
}
+ if(stopOnFirstFailedtestCase != contestInformation.isStopOnFirstFailedtestCase()) {
+ return false;
+ }
+
+ if(memoryLimitInMeg != contestInformation.getMemoryLimitInMeg()) {
+ return false;
+ }
+
+ if(!StringUtilities.stringSame(sandboxCommandLine, contestInformation.getSandboxCommandLine())){
+ return false;
+ }
+
+ if(!StringUtilities.stringSame(overrideLoadAccountsFilename, contestInformation.getOverrideLoadAccountsFilename())){
+ return false;
+ }
+
+ if(loadSampleJudgesData != contestInformation.isLoadSampleJudgesData()) {
+ return false;
+ }
+
+ if(sandboxGraceTimeSecs != contestInformation.getSandboxGraceTimeSecs()) {
+ return false;
+ }
+
+ if(sandboxInteractiveGraceMultiplier != contestInformation.getSandboxInteractiveGraceMultiplier()) {
+ return false;
+ }
+
+ if (scoreboardType != contestInformation.getScoreboardType()) {
+ return false;
+ }
return true;
} catch (Exception e) {
e.printStackTrace(System.err); // TODO log this exception
@@ -931,4 +979,45 @@ public void setSandboxInteractiveGraceMultiplier(int nSecs) {
sandboxInteractiveGraceMultiplier = nSecs;
}
+ /**
+ * Set contest scoreboard type based on the string passed in.
+ *
+ * @param type one of "pass-fail" or "score", currently.
+ */
+ public void setScoreboardType(String type) {
+ for(ScoreboardType sbType : ScoreboardType.values()) {
+ if(type.compareToIgnoreCase(sbType.getType()) == 0) {
+ scoreboardType = sbType;
+ break;
+ }
+ }
+ }
+
+ /**
+ * Accessor for contest scoreboard type. Note there are convenient shorthands
+ * available below.
+ *
+ * @return scoreboardType enum
+ */
+ public ScoreboardType getScoreboardType() {
+ return scoreboardType;
+ }
+
+ /**
+ * Shorthand to determine if the contest is pass-fail scoring.
+ *
+ * @return true if the contest is pass-fail
+ */
+ public boolean isScoreboardTypePassFail() {
+ return (scoreboardType == ScoreboardType.PASSFAIL);
+ }
+
+ /**
+ * Shorthand to determine if the contest is score (point scoring).
+ *
+ * @return true of the contest is point scoring
+ */
+ public boolean isScoreboardTypeScore() {
+ return (scoreboardType == ScoreboardType.SCORE);
+ }
}
diff --git a/src/edu/csus/ecs/pc2/core/model/JudgementRecord.java b/src/edu/csus/ecs/pc2/core/model/JudgementRecord.java
index 6bb3fa8ad..19ee00758 100644
--- a/src/edu/csus/ecs/pc2/core/model/JudgementRecord.java
+++ b/src/edu/csus/ecs/pc2/core/model/JudgementRecord.java
@@ -127,6 +127,8 @@ public class JudgementRecord implements Serializable, IGetDate {
// Copied from submission when judgement record is complete
private Date judgeStartDate = null;
+ // For point scoring contests
+ private double score = 0.0;
/**
* Create a Judgement Record.
@@ -451,4 +453,21 @@ public void setJudgeStartDate(Date startDate) {
public Date getJudgeStartDate() {
return judgeStartDate;
}
+
+ /**
+ *
+ * @return the score, for point scoring contests
+ */
+ public double getScore() {
+ return score;
+ }
+
+ /**
+ * Set the score for point scoring contests
+ *
+ * @param score
+ */
+ public void setScore(double score) {
+ this.score = score;
+ }
}
diff --git a/src/edu/csus/ecs/pc2/core/model/Problem.java b/src/edu/csus/ecs/pc2/core/model/Problem.java
index 9ef3321c8..fb484cd40 100644
--- a/src/edu/csus/ecs/pc2/core/model/Problem.java
+++ b/src/edu/csus/ecs/pc2/core/model/Problem.java
@@ -1,4 +1,4 @@
-// Copyright (C) 1989-2024 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;
@@ -14,6 +14,7 @@
import edu.csus.ecs.pc2.core.log.StaticLog;
import edu.csus.ecs.pc2.core.model.inputValidation.InputValidationResult;
import edu.csus.ecs.pc2.core.model.inputValidation.VivaInputValidatorSettings;
+import edu.csus.ecs.pc2.imports.ccs.TestCaseInfo;
import edu.csus.ecs.pc2.ui.EditProblemSandboxPane;
import edu.csus.ecs.pc2.validator.clicsValidator.ClicsValidatorSettings;
import edu.csus.ecs.pc2.validator.customValidator.CustomValidatorSettings;
@@ -1385,6 +1386,42 @@ public void addTestCaseFilenames (String datafile, String answerfile){
}
}
+ /**
+ * Add data and answer filenames to list of test cases from the TestCaseInfo ArrayList
+ * This is better than adding them one-at-a-time and reallocating the array for each addition.
+ *
+ * @see #removeAllTestCaseFilenames()
+ * @param testCases - all the test cases in a ArrayList
+ */
+ public void addTestCaseFilenames (ArrayList testCases){
+
+ int nCases = testCases.size();
+ testCaseDataFilenames = new String[nCases];
+ testCaseAnswerFilenames = new String[nCases];
+ TestCaseInfo testCase;
+ String groupPath;
+
+ for(int iCase = 0; iCase < nCases; iCase++) {
+ testCase= testCases.get(iCase);
+ testCaseDataFilenames[iCase] = extractFileNameFromPath(testCase.getInputFileName());
+ testCaseAnswerFilenames[iCase] = extractFileNameFromPath(testCase.getAnswerFileName());
+ }
+ }
+
+ /**
+ * Returns last component of a path (the filename).
+ * It relies on the fact that the last component has a File.separator before it. If not,
+ * then the whole string is returned.
+ * @param fileName
+ * @return the filename component of the path
+ */
+ private String extractFileNameFromPath(String fileName) {
+ int idx= fileName.lastIndexOf(File.separator);
+ if(idx == -1) {
+ return(fileName);
+ }
+ return(fileName.substring(idx+1));
+ }
/**
* Remove all test case filenames.
diff --git a/src/edu/csus/ecs/pc2/core/model/ProblemDataFiles.java b/src/edu/csus/ecs/pc2/core/model/ProblemDataFiles.java
index 8e3dca610..63e859573 100644
--- a/src/edu/csus/ecs/pc2/core/model/ProblemDataFiles.java
+++ b/src/edu/csus/ecs/pc2/core/model/ProblemDataFiles.java
@@ -1,26 +1,30 @@
-// 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;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.util.ArrayList;
import edu.csus.ecs.pc2.core.Utilities;
+import edu.csus.ecs.pc2.imports.ccs.TestDataGroup;
+// JB TODO - this will have to be reworked if we intend to update data files AFTER the CDP
+// has been loaded. Specifically, the judgesDataGroups will need to be addressed.
/**
* Data files and programs (validator) for a Problem.
- *
- * Multiple data sets are supported.
+ *
+ * Multiple data sets are supported.
*
- *
- *
+ *
+ *
* @see #getJudgesAnswerFile()
* @see #getJudgesDataFile()
- *
+ *
* @see #getJudgesAnswerFiles()
* @see #getJudgesDataFiles()
- *
+ *
* @see edu.csus.ecs.pc2.core.model.Problem
* @see edu.csus.ecs.pc2.core.model.ProblemDataFilesList
*
@@ -39,9 +43,9 @@ public class ProblemDataFiles implements IElementObject {
private ElementId elementId = new ElementId("ProblemDF");
private ElementId problemId = null;
-
+
private SerializedFile outputValidatorFile;
-
+
private SerializedFile inputValidatorFile;
public ProblemDataFiles(Problem problem) {
@@ -63,10 +67,15 @@ public ProblemDataFiles(Problem problem) {
*/
private SerializedFile[] judgesAnswerFiles = new SerializedFile[0];
+ /**
+ * Data groups for each test file
+ */
+ private TestDataGroup[] judgesDataGroups = new TestDataGroup[0];
+
/**
* This should be invoked with the new Problem and will attempt to copy
* the existing ProblemDataFiles into a new ProblemDataFiles.
- *
+ *
* @param problem
* @return a copy of this ProblemDataFiles
* @throws CloneNotSupportedException
@@ -75,7 +84,7 @@ public ProblemDataFiles copy(Problem problem) throws CloneNotSupportedException
ProblemDataFiles clone = new ProblemDataFiles(problem);
// inherited field
clone.setSiteNumber(getSiteNumber());
-
+
// local fields
// clone.elementId = elementId;
// clone.problemId = getProblemId();
@@ -85,7 +94,9 @@ public ProblemDataFiles copy(Problem problem) throws CloneNotSupportedException
clone.setJudgesAnswerFiles(cloneSFArray(getJudgesAnswerFiles()));
clone.setJudgesDataFiles(cloneSFArray(getJudgesDataFiles()));
-
+ // JB TODO - Does this have to be cloned? I dont think tso.
+ clone.judgesDataGroups = getJudgesDataGroups();
+
return clone;
}
@@ -112,7 +123,7 @@ private SerializedFile cloneSerializedFile(SerializedFile file) throws CloneNotS
/**
* Return array of answer files.
- *
+ *
* @return the judge answer files or a single zero length array.
*/
public SerializedFile[] getJudgesAnswerFiles() {
@@ -121,13 +132,22 @@ public SerializedFile[] getJudgesAnswerFiles() {
/**
* Return array of data files.
- *
+ *
* @return Returns the judge data files or a single zero length array.
*/
public SerializedFile[] getJudgesDataFiles() {
return judgesDataFiles;
}
+ /**
+ * Return array of data groups.
+ *
+ * @return Returns the judge data groups.
+ */
+ public TestDataGroup [] getJudgesDataGroups() {
+ return judgesDataGroups;
+ }
+
/**
* @param judgesAnswerFiles
* The judgesAnswerFiles to set.
@@ -136,6 +156,14 @@ public void setJudgesAnswerFiles(SerializedFile[] judgesAnswerFiles) {
this.judgesAnswerFiles = judgesAnswerFiles;
}
+ /**
+ * @param dataGroups
+ * The dataGroups for each test case.
+ */
+ public void setJudgesDataGroups(ArrayList dataGroups) {
+ judgesDataGroups = dataGroups.toArray(new TestDataGroup [dataGroups.size()]);
+ }
+
/**
* set a single answer file.
*
@@ -171,7 +199,7 @@ public void setJudgesDataFile(SerializedFile judgesDataFile) {
files[0] = judgesDataFile;
setJudgesDataFiles(files);
}
-
+
/**
* Get the judge data file.
* @return a file or null if not present.
@@ -184,10 +212,10 @@ public SerializedFile getJudgesDataFile() {
return files[0];
}
}
-
+
/**
* Get the judge answer file.
- *
+ *
* @return a file or null if not present.
*/
public SerializedFile getJudgesAnswerFile() {
@@ -202,18 +230,22 @@ public SerializedFile getJudgesAnswerFile() {
/**
* @return Returns the elementId.
*/
+ @Override
public ElementId getElementId() {
return elementId;
}
+ @Override
public int versionNumber() {
return elementId.getVersionNumber();
}
+ @Override
public int getSiteNumber() {
return elementId.getSiteNumber();
}
+ @Override
public void setSiteNumber(int siteNumber) {
elementId.setSiteNumber(siteNumber);
}
@@ -225,10 +257,10 @@ public ElementId getProblemId() {
protected void setProblemId(ElementId problemId) {
this.problemId = problemId;
}
-
+
/**
* Returns the output validator program file.
- *
+ *
* @return a SerializedFile containing the output validator code, or null if no output validator has been specified
*/
public SerializedFile getOutputValidatorFile() {
@@ -238,10 +270,10 @@ public SerializedFile getOutputValidatorFile() {
public void setOutputValidatorFile(SerializedFile validatorFile) {
this.outputValidatorFile = validatorFile;
}
-
+
/**
* Returns the custom input validator program file associated with this ProblemDataFiles object.
- *
+ *
* @return a SerializedFile containing the input validator code, or null if no custom input validator has been specified.
*/
public SerializedFile getCustomInputValidatorFile() {
@@ -250,14 +282,14 @@ public SerializedFile getCustomInputValidatorFile() {
/**
* Sets the custom input validator program file associated with this ProblemDataFiles object.
- *
+ *
* @param validatorFile a SerializedFile containing the custom input validator program to be associated with this ProblemDataFiles.
*/
public void setCustomInputValidatorFile(SerializedFile validatorFile) {
this.inputValidatorFile = validatorFile;
}
-
-
+
+
private boolean compareSerializedFiles(SerializedFile oldFile, SerializedFile newFile) {
if (oldFile == null) {
@@ -295,7 +327,7 @@ private boolean compareSerializedFiles(SerializedFile oldFile, SerializedFile ne
}
return true;
}
-
+
private boolean compareSerializedFileArrays(SerializedFile[] oldList, SerializedFile[] newList) {
if (oldList == null) {
return(newList == null);
@@ -315,7 +347,7 @@ private boolean compareSerializedFileArrays(SerializedFile[] oldList, Serialized
}
return true;
}
-
+
public boolean isSameAs(ProblemDataFiles newProblemDataFiles) {
try {
if (newProblemDataFiles == null) {
@@ -333,9 +365,9 @@ public boolean isSameAs(ProblemDataFiles newProblemDataFiles) {
if (!compareSerializedFiles(this.getCustomInputValidatorFile(), newProblemDataFiles.getCustomInputValidatorFile())) {
return false;
}
-
+
// TODO 917 should compare the other problemDataFile fields too.
-
+
return true;
} catch (Exception e) {
// TODO Log to static exception Log
@@ -343,7 +375,7 @@ public boolean isSameAs(ProblemDataFiles newProblemDataFiles) {
return false;
}
}
-
+
@Override
public String toString() {
@@ -354,20 +386,20 @@ public String toString() {
int numDataFiles = judgesDataFiles.length;
int numAnsFiles = judgesAnswerFiles.length;
-
+
buf.append(numDataFiles);
buf.append(" data files, ");
-
+
buf.append(numAnsFiles);
buf.append(" answer files. ");
-
+
buf.append("Data: ");
SerializedFile [] list = judgesDataFiles;
for (SerializedFile file : list) {
buf.append(file.getName());
buf.append(" ");
}
-
+
buf.append("Answer: ");
list = judgesAnswerFiles;
for (SerializedFile file : list) {
@@ -395,27 +427,27 @@ public String toString() {
return buf.toString();
}
-
-
+
+
/**
* Returns the full path for judge data filenames.
- *
+ *
* Expected locations vary depending on the client type (Admin or Judge) and
* whether the problem has external or internal files.
- *
+ *
* See {@link Utilities#getProblemfullFilenames(IInternalContest, Problem, SerializedFile[], String)} for details.
- *
+ *
*
* Sample code: for Judge
* String [] filenames = Utilities.getFullJudgesDataFilenames(contest, executable.getExecuteDirectoryName());
- *
+ *
* Sample code: for Admin
* String [] filenames = Utilities.getFullJudgesDataFilenames(contest, null);
- *
+ *
*
- *
+ *
* @param contest
- * @param executableDir
+ * @param executableDir
*/
public String[] getFullJudgesDataFilenames(IInternalContest contest, String executableDir) {
return Utilities.fullJudgesDataFilenames(contest, this, executableDir);
@@ -423,22 +455,22 @@ public String[] getFullJudgesDataFilenames(IInternalContest contest, String exec
/**
* Returns the full path for judge answer filenames.
- *
+ *
*
* Sample code:
* String [] filenames = Utilities.getFullJudgesAnswerFilenames(contest, executable.getExecuteDirectoryName());
- *
- *
+ *
+ *
* @param contest
* @param executableDir
*/
public String[] getFullJudgesAnswerFilenames(IInternalContest contest, String executableDir) {
return Utilities.fullJudgesAnswerFilenames(contest, this, executableDir);
}
-
+
/**
* Check for existence of all judge data and answer files, may create data files if needed.
- *
+ *
* Will not create files if problem has external files, {@link Problem#isUsingExternalDataFiles()} set true.
*
* Will create judges answer and and data files (in exedcutableDir) if problem has internal files.
@@ -447,8 +479,8 @@ public String[] getFullJudgesAnswerFilenames(IInternalContest contest, String ex
*
* Sample code:
* checkAndCreateFiles(contest, executable.getExecuteDirectoryName());
- *
- *
+ *
+ *
* @param contest
* @param executableDir directory where internal files are expected.
* @throws FileNotFoundException if external files not found or cannot create internal file
@@ -507,7 +539,7 @@ public void checkAndCreateFiles(IInternalContest contest, String executableDir)
/**
* Remove data set from problem data sets.
- *
+ *
* @param index zero based data set row
*/
public void removeDataSet(int index) {
diff --git a/src/edu/csus/ecs/pc2/core/scoring/DefaultPointScoringStandingsRecordComparator.java b/src/edu/csus/ecs/pc2/core/scoring/DefaultPointScoringStandingsRecordComparator.java
new file mode 100644
index 000000000..0e910866c
--- /dev/null
+++ b/src/edu/csus/ecs/pc2/core/scoring/DefaultPointScoringStandingsRecordComparator.java
@@ -0,0 +1,120 @@
+// 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.AccountNameComparator;
+import edu.csus.ecs.pc2.core.model.Account;
+
+/**
+ * Sorts StandingsRecord according to the Legacy Problem Package Format point scoring specification (such as it is)
+ *
+ * @author John Buck
+ */
+
+// $HeadURL$
+public class DefaultPointScoringStandingsRecordComparator implements Serializable, Comparator {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+
+ private AccountNameComparator accountNameComparator = new AccountNameComparator();
+
+ 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;
+ long aLastSolvedTime, bLastSolvedTime;
+ double aScore, bScore;
+ int aClientHash, bClientHash;
+ String nameA, nameB;
+ int nameComparison;
+
+ StandingsRecord teamA = o1;
+ StandingsRecord teamB = o2;
+ aScore = teamA.getScore();
+ aLastSolvedTime = teamA.getLastSolved();
+ Account accountA = cachedAccountList.getAccount(teamA.getClientId());
+ nameA = accountA.getDisplayName();
+ aClientHash = teamA.getClientId().hashCode();
+ bScore = teamB.getScore();
+ bLastSolvedTime = teamB.getLastSolved();
+ Account accountB = cachedAccountList.getAccount(teamB.getClientId());
+ nameB = accountB.getDisplayName();
+ bClientHash = teamB.getClientId().hashCode();
+ nameComparison = accountNameComparator.compare(nameA.toLowerCase(), nameB.toLowerCase());
+
+ //
+ // Primary Sort = score (high to low)
+ // Secondary Sort = earliest submittal of last submission (low to high)
+ // Third Sort = teamName (low to high)
+ // Fourth Sort = clientId (low to high)
+
+ if ((bScore == aScore) && (bLastSolvedTime == aLastSolvedTime) && (nameComparison == 0)
+ && (bClientHash == aClientHash)) {
+ status = 0; // elements equal, this shouldn't happen, Tammy...
+ } else {
+ // The sorting algorithm sorts things from "low to high" by default (ascending).
+ // The comparators should return a value as to whether the first thing (A) should be
+ // considered(!) less-than, equal-to or greater-than the 2nd thing (B). That is,
+ // does A appear AFTER B in the sorted result.
+ // For Point Scoring, the item with the biggest score should be the first thing
+ // in the result. When comparing 2 things (ascore and bscore), we have to determine
+ // if we want ascore to come after bscore. (IE if ascore is smaller than bscore,
+ // then A comes after B, and should be considered that A is bigger than B
+ // in terms of how the sorting is done, so we return 1, indicating that A should
+ // come after B in the sorted list.
+ if ((bScore > aScore)
+ || ((bScore == aScore) && (bLastSolvedTime < aLastSolvedTime))
+ || ((bScore == aScore) && (bLastSolvedTime == aLastSolvedTime) && (nameComparison > 0))
+ || ((bScore == aScore) && (bLastSolvedTime == aLastSolvedTime)
+ && (nameComparison == 0) && (bClientHash < aClientHash))) {
+ status = 1; // a to be considered greater than b
+ } else {
+ status = -1; // a to be considered less than 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/DefaultScoringAlgorithm.java b/src/edu/csus/ecs/pc2/core/scoring/DefaultScoringAlgorithm.java
index 9596e3fe0..97cbadccb 100644
--- a/src/edu/csus/ecs/pc2/core/scoring/DefaultScoringAlgorithm.java
+++ b/src/edu/csus/ecs/pc2/core/scoring/DefaultScoringAlgorithm.java
@@ -6,6 +6,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Comparator;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
@@ -39,6 +40,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.Run.RunStates;
@@ -177,9 +179,10 @@ AccountList getAccountList(IInternalContest theContest) {
/**
* Get the Score and Statistics information for one problem.
*
- * @return pc2.ex.ProblemScoreData
- * @param treeMap
- * java.util.TreeMap
+ * @param treeMap java.util.TreeMap of filtered Runs
+ * @param theContest
+ * @return ProblemSummaryInfo
+ * @throws IllegalContestState
*/
private ProblemSummaryInfo calcProblemScoreData(TreeMap treeMap, IInternalContest theContest) throws IllegalContestState {
ProblemSummaryInfo problemSummaryInfo = new ProblemSummaryInfo();
@@ -245,6 +248,73 @@ private ProblemSummaryInfo calcProblemScoreData(TreeMap treeMap, IInter
return problemSummaryInfo;
}
+ /**
+ * Get the Point Scoring and Statistics information for one problem.
+ *
+ * @param treeMap java.util.TreeMap of filtered Runs
+ * @param theContest
+ * @return ProblemSummaryInfo
+ * @throws IllegalContestState
+ */
+ private ProblemSummaryInfo calcProblemPointScoreData(TreeMap treeMap, IInternalContest theContest) throws IllegalContestState {
+ ProblemSummaryInfo problemSummaryInfo = new ProblemSummaryInfo();
+ double score = 0.0;
+ int attempts = 0;
+ ElementId problemId = null;
+ long solutionTime = -1;
+ boolean solved = false;
+ boolean unJudgedRun = false;
+ JudgementRecord judgmentRecord;
+
+ if (treeMap.isEmpty()) {
+ problemSummaryInfo = null; // ProblemScoreData must have ProblemId to be valid
+ } else {
+ Collection coll = treeMap.values();
+ Object[] o;
+ Run run;
+ o = coll.toArray();
+ for (int i = 0; i < o.length; i++) {
+ run = (Run) o[i];
+ // this should not have made it into the incoming treeMap
+ if (run.isDeleted()) {
+ continue;
+ }
+ attempts++;
+ problemId = run.getProblemId();
+ // added isValidJudgement to check and obey preliminary results
+ if (run.isSolved() && isValidJudgement(run)) {
+ // TODO: we might want some differing logic here if all
+ // yes's are counted
+ // and/or no's after yes's are counted
+ solved = true;
+ solutionTime = run.getElapsedMins();
+ judgmentRecord = run.getJudgementRecord();
+ if(judgmentRecord != null) {
+ score = judgmentRecord.getScore();
+ }
+ break;
+ } else {
+ // we should really only do this if it's been judged
+ if (!isValidJudgement(run)) {
+ unJudgedRun = true;
+ }
+ }
+ }
+ }
+ // TODO put another if around this if there was a setting to include all
+ // no's before yes
+ if (!solved) {
+ score = 0.0;
+ }
+ problemSummaryInfo.setSolved(solved);
+ problemSummaryInfo.setSolutionTime(solutionTime);
+ problemSummaryInfo.setProblemId(problemId);
+ problemSummaryInfo.setNumberSubmitted(attempts);
+ problemSummaryInfo.setScore(score);
+ problemSummaryInfo.setUnJudgedRuns(unJudgedRun);
+ return problemSummaryInfo;
+ }
+
/**
* @param key
* property to lookup
@@ -479,11 +549,27 @@ public String getStandings(IInternalContest theContest, Run[] runs, Integer divi
} // else no runs
- applyScoringAdjustments(standingsRecordHash, accountList);
+ Comparator src;
+
+ // 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(theContest.getContestInformation().isScoreboardTypePassFail()) {
+ // Not applicable for point scoring contests, is it? JB
+ applyScoringAdjustments(standingsRecordHash, accountList);
+ DefaultStandingsRecordComparator srcPassFailRecordComparator = new DefaultStandingsRecordComparator();
+ srcPassFailRecordComparator.setCachedAccountList(accountList);
+ src = srcPassFailRecordComparator;
+
+ } else {
+ DefaultPointScoringStandingsRecordComparator srcPointScoringRecordComparator = new DefaultPointScoringStandingsRecordComparator();
+ srcPointScoringRecordComparator.setCachedAccountList(accountList);
+ src = srcPointScoringRecordComparator;
+ }
// use TreeMap to sort
- DefaultStandingsRecordComparator src = new DefaultStandingsRecordComparator();
- src.setCachedAccountList(accountList);
TreeMap treeMap = new TreeMap(src);
Collection enumeration = standingsRecordHash.values();
for (StandingsRecord record : enumeration) {
@@ -639,6 +725,8 @@ private void createStandingXML (TreeMap treeMa
IInternalContest theContest, IMemento summaryMememento, boolean excludedGroups) {
ContestInformation contestInformation = theContest.getContestInformation();
+ boolean isPointScoring = contestInformation.isScoreboardTypeScore();
+
// easy access
Hashtable groupHash = new Hashtable();
Hashtable groupIndexHash = new Hashtable();
@@ -695,6 +783,7 @@ private void createStandingXML (TreeMap treeMa
// assign the ranks
long numSolved = -1, score = 0, lastSolved = 0;
+ double pointScore = 0.0;
int rank = 0, indexRank = 0;
int index = 0;
// these are indexed by groupIndex
@@ -703,6 +792,7 @@ private void createStandingXML (TreeMap treeMa
groupNumSolved[i] = -1;
}
long[] groupScore = new long[groupCount];
+ double[] groupPointScore = new double[groupCount];
long[] groupLastSolved = new long[groupCount];
int[] groupRank = new int[groupCount];
int[] groupIndexRank = new int[groupCount];
@@ -711,6 +801,7 @@ private void createStandingXML (TreeMap treeMa
groupLastSolved[i] = 0;
groupRank[i] = 0;
groupIndexRank[i] = 0;
+ groupPointScore[i] = 0.0;
}
long[] divisionNumSolved = new long[divisionCount];
for (int i = 0; i < divisionCount; i++) {
@@ -719,12 +810,14 @@ private void createStandingXML (TreeMap treeMa
int[] divisionRank = new int[divisionCount];
int[] divisionIndexRank = new int[divisionCount];
long[] divisionScore = new long[divisionCount];
+ double[] divisionPointScore = new double[divisionCount];
long[] divisionLastSolved = new long[divisionCount];
for (int i = 0; i < divisionCount; i++) {
divisionRank[i] = 0;
divisionIndexRank[i] = 0;
divisionScore[i] = 0;
divisionLastSolved[i] = 0;
+ divisionPointScore[i] = 0.0;
}
RunStatistics runStats = new RunStatistics(theContest);
@@ -736,9 +829,10 @@ private void createStandingXML (TreeMap treeMa
StandingsRecord standingsRecord = (StandingsRecord)o;
indexRank++;
- if (!isTeamTied(standingsRecord, numSolved, score, lastSolved)) {
+ if (!isTeamTied(standingsRecord, isPointScoring, pointScore, numSolved, score, lastSolved)) {
numSolved = standingsRecord.getNumberSolved();
score = standingsRecord.getPenaltyPoints();
+ pointScore = standingsRecord.getScore();
lastSolved = standingsRecord.getLastSolved();
rank = indexRank;
standingsRecord.setRankNumber(rank);
@@ -757,6 +851,7 @@ private void createStandingXML (TreeMap treeMa
standingsRecordMemento.putLong("firstSolved", standingsRecord.getFirstSolved());
standingsRecordMemento.putLong("lastSolved", standingsRecord.getLastSolved());
standingsRecordMemento.putLong("points", standingsRecord.getPenaltyPoints());
+ standingsRecordMemento.putDouble("score", standingsRecord.getScore());
standingsRecordMemento.putInteger("solved", standingsRecord.getNumberSolved());
standingsRecordMemento.putInteger("rank", teamRank);
standingsRecordMemento.putInteger("overallRank", teamRank);
@@ -797,9 +892,10 @@ private void createStandingXML (TreeMap treeMa
int groupIndex = groupIndexHash.get(group).intValue();
// do the same thing as above, now for the group
groupIndexRank[groupIndex]++;
- if (!isTeamTied(standingsRecord,groupNumSolved[groupIndex], groupScore[groupIndex],groupLastSolved[groupIndex])) {
+ if (!isTeamTied(standingsRecord, isPointScoring, groupPointScore[groupIndex],groupNumSolved[groupIndex], groupScore[groupIndex],groupLastSolved[groupIndex])) {
groupNumSolved[groupIndex] = standingsRecord.getNumberSolved();
groupScore[groupIndex] = standingsRecord.getPenaltyPoints();
+ groupPointScore[groupIndex] = standingsRecord.getScore();
groupLastSolved[groupIndex] = standingsRecord.getLastSolved();
groupRank[groupIndex] = groupIndexRank[groupIndex];
standingsRecord.setGroupRankNumber(groupRank[groupIndex]);
@@ -816,9 +912,10 @@ private void createStandingXML (TreeMap treeMa
if (divisionIndexHash.containsKey(group)) {
int divisionIndex = divisionIndexHash.get(group).intValue()-1;
divisionIndexRank[divisionIndex]++;
- if (!isTeamTied(standingsRecord, divisionNumSolved[divisionIndex], divisionScore[divisionIndex],divisionLastSolved[divisionIndex])) {
+ if (!isTeamTied(standingsRecord, isPointScoring, divisionPointScore[divisionIndex], divisionNumSolved[divisionIndex], divisionScore[divisionIndex],divisionLastSolved[divisionIndex])) {
divisionNumSolved[divisionIndex] = standingsRecord.getNumberSolved();
divisionScore[divisionIndex] = standingsRecord.getPenaltyPoints();
+ divisionPointScore[divisionIndex] = standingsRecord.getScore();
divisionLastSolved[divisionIndex] = standingsRecord.getLastSolved();
divisionRank[divisionIndex] = divisionIndexRank[divisionIndex];
standingsRecord.setDivisionRankNumber(divisionRank[divisionIndex]);
@@ -846,6 +943,7 @@ private void createStandingXML (TreeMap treeMa
psiMemento.putString("shortName", problems[problemsIndexHash.get(psi.getProblemId())-1].getShortName());
psiMemento.putInteger("attempts", psi.getNumberSubmitted());
psiMemento.putInteger("points", psi.getPenaltyPoints());
+ psiMemento.putDouble("score", psi.getScore());
psiMemento.putLong("solutionTime", psi.getSolutionTime());
psiMemento.putBoolean("isSolved", psi.isSolved());
psiMemento.putBoolean("isPending", psi.isUnJudgedRuns());
@@ -984,12 +1082,18 @@ boolean isValidJudgement(Run run) {
* @param lastSolved
* @return True if the long parameters match the corresponding numbers in the StandingsRecord
*/
- boolean isTeamTied(StandingsRecord standingsRecord, long numSolved, long score, long lastSolved) {
- if (numSolved != standingsRecord.getNumberSolved()) {
- return false;
- }
- if (score != standingsRecord.getPenaltyPoints()) {
- return false;
+ boolean isTeamTied(StandingsRecord standingsRecord, boolean isPointScoring, double pointScore, long numSolved, long score, long lastSolved) {
+ if(isPointScoring) {
+ if(pointScore != standingsRecord.getScore()) {
+ return false;
+ }
+ } else {
+ if (numSolved != standingsRecord.getNumberSolved()) {
+ return false;
+ }
+ if (score != standingsRecord.getPenaltyPoints()) {
+ return false;
+ }
}
if (lastSolved != standingsRecord.getLastSolved()) {
return false;
@@ -1058,18 +1162,32 @@ private void generateStandingsValues(final TreeMap runTreeMap, Hashtab
// cannot be null for 1st run
String lastUser = "";
String lastProblem = "";
+ double dScore;
+ boolean isPointScoreContest = theContest.getContestInformation().isScoreboardTypeScore();
while (runIterator.hasNext()) {
Object o = runIterator.next();
Run run = (Run) o;
if (!lastUser.equals(run.getSubmitter().toString()) || !lastProblem.equals(run.getProblemId().toString())) {
if (!problemTreeMap.isEmpty()) {
- ProblemSummaryInfo problemSummaryInfo = calcProblemScoreData(problemTreeMap, theContest);
+ ProblemSummaryInfo problemSummaryInfo;
+ if(isPointScoreContest) {
+ problemSummaryInfo = calcProblemPointScoreData(problemTreeMap, theContest);
+ } else {
+ problemSummaryInfo = calcProblemScoreData(problemTreeMap, theContest);
+ }
StandingsRecord standingsRecord = standingsHash.get(lastUser);
SummaryRow summaryRow = standingsRecord.getSummaryRow();
summaryRow.put(problemsHash.get(problemSummaryInfo.getProblemId()), problemSummaryInfo);
standingsRecord.setSummaryRow(summaryRow);
- standingsRecord.setPenaltyPoints(standingsRecord.getPenaltyPoints() + problemSummaryInfo.getPenaltyPoints());
+ if(isPointScoreContest) {
+ // we just sum them up
+ dScore = problemSummaryInfo.getScore();
+ standingsRecord.setScore(standingsRecord.getScore() + dScore);
+ } else {
+ standingsRecord.setPenaltyPoints(standingsRecord.getPenaltyPoints() + problemSummaryInfo.getPenaltyPoints());
+ }
+
if (problemSummaryInfo.isSolved()) {
standingsRecord.setNumberSolved(standingsRecord.getNumberSolved() + 1);
oldTime = standingsRecord.getLastSolved();
@@ -1093,12 +1211,25 @@ private void generateStandingsValues(final TreeMap runTreeMap, Hashtab
// handle last run
if (!problemTreeMap.isEmpty()) {
- ProblemSummaryInfo problemSummaryInfo = calcProblemScoreData(problemTreeMap, theContest);
+ ProblemSummaryInfo problemSummaryInfo;
+ if(isPointScoreContest) {
+ problemSummaryInfo = calcProblemPointScoreData(problemTreeMap, theContest);
+ } else {
+ problemSummaryInfo = calcProblemScoreData(problemTreeMap, theContest);
+ }
StandingsRecord standingsRecord = standingsHash.get(lastUser);
SummaryRow summaryRow = standingsRecord.getSummaryRow();
summaryRow.put(problemsHash.get(problemSummaryInfo.getProblemId()), problemSummaryInfo);
standingsRecord.setSummaryRow(summaryRow);
- standingsRecord.setPenaltyPoints(standingsRecord.getPenaltyPoints() + problemSummaryInfo.getPenaltyPoints());
+ if(isPointScoreContest) {
+ // we just care about the biggest score for the problem.
+ dScore = problemSummaryInfo.getScore();
+ if(dScore > standingsRecord.getScore()) {
+ standingsRecord.setScore(dScore);
+ }
+ } else {
+ standingsRecord.setPenaltyPoints(standingsRecord.getPenaltyPoints() + problemSummaryInfo.getPenaltyPoints());
+ }
if (problemSummaryInfo.isSolved()) {
standingsRecord.setNumberSolved(standingsRecord.getNumberSolved() + 1);
oldTime = standingsRecord.getLastSolved();
@@ -1157,6 +1288,7 @@ private void initializeStandingsRecordHash(IInternalContest theContest, AccountL
ProblemSummaryInfo problemSummaryInfo = new ProblemSummaryInfo();
problemSummaryInfo.setProblemId(problems[j].getElementId());
problemSummaryInfo.setPenaltyPoints(0);
+ problemSummaryInfo.setScore(0.0);
summaryRow.put(j + 1, problemSummaryInfo);
}
standingsRecord.setSummaryRow(summaryRow);
@@ -1188,6 +1320,7 @@ private IMemento createSummaryMomento(IInternalContest contest, XMLMemento memen
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$");
// bug 1540
String value = "Live (unfrozen) scoreboard";
diff --git a/src/edu/csus/ecs/pc2/core/scoring/ProblemSummaryInfo.java b/src/edu/csus/ecs/pc2/core/scoring/ProblemSummaryInfo.java
index e7a5d4d6e..b3e5a28c5 100644
--- a/src/edu/csus/ecs/pc2/core/scoring/ProblemSummaryInfo.java
+++ b/src/edu/csus/ecs/pc2/core/scoring/ProblemSummaryInfo.java
@@ -1,4 +1,4 @@
-// Copyright (C) 1989-2024 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.scoring;
import java.io.Serializable;
@@ -7,7 +7,7 @@
/**
* Problem (Scoring) Summary Info.
- *
+ *
* @author pc2@ecs.csus.edu
* @version $Id$
*/
@@ -16,7 +16,7 @@
public class ProblemSummaryInfo implements Serializable {
/**
- *
+ *
*/
private static final long serialVersionUID = -4157597862536104668L;
@@ -28,6 +28,8 @@ public class ProblemSummaryInfo implements Serializable {
private int penaltyPoints = 0;
+ private double score = 0.0;
+
private boolean unJudgedRuns = false;
private int pendingRunCount = 0;
@@ -35,7 +37,7 @@ public class ProblemSummaryInfo implements Serializable {
private int judgedRunCount = 0;
private ElementId problemId = null;
-
+
private String shortName = "";
/**
@@ -52,11 +54,11 @@ public ElementId getProblemId() {
public void setProblemId(edu.csus.ecs.pc2.core.model.ElementId problemId) {
this.problemId = problemId;
}
-
+
public String getShortName() {
return shortName;
}
-
+
public void setShortName(String shortName) {
this.shortName = shortName;
}
@@ -145,4 +147,18 @@ public int getJudgedRunCount() {
return judgedRunCount;
}
+ /**
+ * @return the score
+ */
+ public double getScore() {
+ return score;
+ }
+
+ /**
+ * @param score the score to set
+ */
+ public void setScore(double score) {
+ this.score = score;
+ }
+
}
diff --git a/src/edu/csus/ecs/pc2/core/scoring/StandingsRecord.java b/src/edu/csus/ecs/pc2/core/scoring/StandingsRecord.java
index e1b8be745..2ff105e15 100644
--- a/src/edu/csus/ecs/pc2/core/scoring/StandingsRecord.java
+++ b/src/edu/csus/ecs/pc2/core/scoring/StandingsRecord.java
@@ -1,10 +1,11 @@
-// 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.scoring;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
+import edu.csus.ecs.pc2.api.BaseClient;
import edu.csus.ecs.pc2.core.model.ClientId;
/**
@@ -31,13 +32,19 @@ public class StandingsRecord {
* Rank within the Group.
*/
private int groupRankNumber = 0;
-
+
/**
* Penalty Points.
*
*/
private long penaltyPoints;
+ /**
+ * Point score
+ *
+ */
+ private double score;
+
/**
* Number of problems solved.
*/
@@ -192,14 +199,14 @@ public int getGroupRankNumber() {
public void setGroupRankNumber(int groupRankNumber) {
this.groupRankNumber = groupRankNumber;
}
-
+
/**
* Returns a String representation of this object in JSON format.
*/
@Override
public String toString() {
ObjectMapper mapper = new ObjectMapper();
-
+
String jsonString = "{}";
try {
jsonString = mapper.writeValueAsString(this);
@@ -220,5 +227,18 @@ public void setDivisionRankNumber(int divisionRankNumber) {
this.divisionRankNumber = divisionRankNumber;
}
+ /**
+ * @return the score
+ */
+ public double getScore() {
+ return score;
+ }
+
+ /**
+ * @param score the score to set
+ */
+ public void setScore(double score) {
+ this.score = score;
+ }
}
diff --git a/src/edu/csus/ecs/pc2/core/standings/ProblemSummaryInfo.java b/src/edu/csus/ecs/pc2/core/standings/ProblemSummaryInfo.java
index 1c9480927..24ea2fcf1 100644
--- a/src/edu/csus/ecs/pc2/core/standings/ProblemSummaryInfo.java
+++ b/src/edu/csus/ecs/pc2/core/standings/ProblemSummaryInfo.java
@@ -38,6 +38,9 @@ public class ProblemSummaryInfo {
@XmlAttribute
private String points;
+ @XmlAttribute
+ private String score;
+
@XmlAttribute
private String problemId;
@@ -111,4 +114,18 @@ public void setSolutionTime(String solutionTime) {
this.solutionTime = solutionTime;
}
+ /**
+ * @return the score
+ */
+ public String getScore() {
+ return score;
+ }
+
+ /**
+ * @param score the score to set
+ */
+ public void setScore(String score) {
+ this.score = score;
+ }
+
}
diff --git a/src/edu/csus/ecs/pc2/core/standings/TeamStanding.java b/src/edu/csus/ecs/pc2/core/standings/TeamStanding.java
index afa7e19ad..92fd2a34b 100644
--- a/src/edu/csus/ecs/pc2/core/standings/TeamStanding.java
+++ b/src/edu/csus/ecs/pc2/core/standings/TeamStanding.java
@@ -14,12 +14,12 @@
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
/**
- * This class defines an entry (one for each team) which appears in a List of TeamStandings in a {@link ContestStandings} object.
+ * This class defines an entry (one for each team) which appears in a List of TeamStandings in a {@link ContestStandings} object.
* (A {@link ContestStandings} consists of a single {@link StandingsHeader} followed by a List of {@link TeamStanding}s.)
* This class is used as a target during conversion (deserialization) of an XML representation of a TeamStanding into a POJO.
- *
- * Note that the @JsonIgnoreProperties(ignoreUnknown=true) annotation is supplied in the event the XML returned by the
- * DefaultScoringAlgorithm class (which is frequently converted to a ContestStandings object using, for example,
+ *
+ * Note that the @JsonIgnoreProperties(ignoreUnknown=true) annotation is supplied in the event the XML returned by the
+ * DefaultScoringAlgorithm class (which is frequently converted to a ContestStandings object using, for example,
* the Jackson XMLMapper class), contains attributes which this class doesn't define.
*
* @author Douglas A. Lane, John Clevenger,
@@ -50,6 +50,9 @@ public class TeamStanding {
@XmlAttribute
private String points;
+ @XmlAttribute
+ private String score;
+
@XmlAttribute
private String problemsAttempted;
@@ -266,8 +269,18 @@ public String getTotalAttempts() {
public void setTotalAttempts(String totalAttempts) {
this.totalAttempts = totalAttempts;
}
-
-
+ /**
+ * @return the score
+ */
+ public String getScore() {
+ return score;
+ }
+ /**
+ * @param score the score to set
+ */
+ public void setScore(String score) {
+ this.score = score;
+ }
}
diff --git a/src/edu/csus/ecs/pc2/core/util/IMemento.java b/src/edu/csus/ecs/pc2/core/util/IMemento.java
index c2e91b333..b67e6e2d9 100644
--- a/src/edu/csus/ecs/pc2/core/util/IMemento.java
+++ b/src/edu/csus/ecs/pc2/core/util/IMemento.java
@@ -1,11 +1,11 @@
-// 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.
/**********************************************************************
* Copyright (c) 2003, 2005 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
- *
+ *
* Contributors:
* IBM Corporation - Initial API and implementation
**********************************************************************/
@@ -34,7 +34,7 @@
*
* This interface is not intended to be implemented by clients.
*
- *
+ *
* @author pc2@ecs.csus.edu
* @version $Id$
*/
@@ -43,7 +43,7 @@
public interface IMemento {
/**
* Special reserved key used to store the memento id (value "org.eclipse.ui.id").
- *
+ *
* @see #getId
*/
String TAG_ID = "IMemento.internal.id"; //$NON-NLS-1$
@@ -53,7 +53,7 @@ public interface IMemento {
*
* The getChild and getChildren methods are used to retrieve children of a given type.
*
- *
+ *
* @param type
* the type
* @return a new child memento
@@ -68,7 +68,7 @@ public interface IMemento {
*
* The getChild and getChildren methods are used to retrieve children of a given type.
*
- *
+ *
* @param type
* the type
* @param id
@@ -80,7 +80,7 @@ public interface IMemento {
/**
* Returns the first child with the given type id.
- *
+ *
* @param type
* the type id
* @return the first child with the given type
@@ -89,7 +89,7 @@ public interface IMemento {
/**
* Returns all children with the given type id.
- *
+ *
* @param type
* the type id
* @return the list of children with the given type
@@ -98,16 +98,25 @@ public interface IMemento {
/**
* Returns the floating point value of the given key.
- *
+ *
* @param key
* the key
* @return the value, or null if the key was not found or was found but was not a floating point number
*/
Float getFloat(String key);
+ /**
+ * Returns the double precision point value of the given key.
+ *
+ * @param key
+ * the key
+ * @return the value, or null if the key was not found or was found but was not a double precision point number
+ */
+ Double getDouble(String key);
+
/**
* Returns the id for this memento.
- *
+ *
* @return the memento id, or null if none
* @see #createChild(java.lang.String,java.lang.String)
*/
@@ -115,7 +124,7 @@ public interface IMemento {
/**
* Returns the name for this memento.
- *
+ *
* @return the memento name, or null if none
* @see #createChild(java.lang.String,java.lang.String)
*/
@@ -123,7 +132,7 @@ public interface IMemento {
/**
* Returns the integer value of the given key.
- *
+ *
* @param key
* the key
* @return the value, or null if the key was not found or was found but was not an integer
@@ -132,7 +141,7 @@ public interface IMemento {
/**
* Returns the long value of the given key.
- *
+ *
* @param key
* the key
* @return the value, or null if the key was not found or was found but was not an integer
@@ -141,7 +150,7 @@ public interface IMemento {
/**
* Returns the string value of the given key.
- *
+ *
* @param key
* the key
* @return the value, or null if the key was not found or was found but was not an integer
@@ -150,7 +159,7 @@ public interface IMemento {
/**
* Returns the boolean value of the given key.
- *
+ *
* @param key
* the key
* @return the value, or null if the key was not found or was found but was not a boolean
@@ -159,14 +168,14 @@ public interface IMemento {
/**
* Return the list of names.
- *
+ *
* @return a possibly empty list of names
*/
List getNames();
/**
* Sets the value of the given key to the given floating point number.
- *
+ *
* @param key
* the key
* @param value
@@ -174,9 +183,19 @@ public interface IMemento {
*/
void putFloat(String key, float value);
+ /**
+ * Sets the value of the given key to the given double precision point number.
+ *
+ * @param key
+ * the key
+ * @param value
+ * the value
+ */
+ void putDouble(String key, double value);
+
/**
* Sets the value of the given key to the given integer.
- *
+ *
* @param key
* the key
* @param value
@@ -186,7 +205,7 @@ public interface IMemento {
/**
* Sets the value of the given key to the given long.
- *
+ *
* @param key
* the key
* @param value
@@ -196,7 +215,7 @@ public interface IMemento {
/**
* Sets the value of the given key to the given boolean value.
- *
+ *
* @param key
* the key
* @param value
@@ -206,7 +225,7 @@ public interface IMemento {
/**
* Copy the attributes and children from memento to the receiver.
- *
+ *
* @param memento
* the IMemento to be copied.
*/
@@ -214,7 +233,7 @@ public interface IMemento {
/**
* Sets the value of the given key to the given string.
- *
+ *
* @param key
* the key
* @param value
@@ -224,17 +243,17 @@ public interface IMemento {
/**
* Create an element with a value.
- *
+ *
* {@link #createChild(String)} and {@link #createChild(String, String)} do not create an XML element with a value.
*
* For example createChild("name", "Jenny") will create the following XML:
- *
+ *
*
<name IMemento.internal.id="Jenny>
- *
+ *
* This method places a value into the element, for example to create and element use the following createChildNode("name", "Jenny")
- *
+ *
* <name>Jenny</name>
- *
+ *
* @see #getValue()
* @param name
* name for element
@@ -246,11 +265,11 @@ public interface IMemento {
/**
* Get value/String for element.
- *
+ *
* Returns the value for the element. Ex.
- *
+ *
* <name>Troy</name>
- *
+ *
* getValue() would return: Troy
*
* @see #createChildNode(String, String)
diff --git a/src/edu/csus/ecs/pc2/core/util/XMLMemento.java b/src/edu/csus/ecs/pc2/core/util/XMLMemento.java
index bd95208fc..9aeacbd14 100644
--- a/src/edu/csus/ecs/pc2/core/util/XMLMemento.java
+++ b/src/edu/csus/ecs/pc2/core/util/XMLMemento.java
@@ -1,11 +1,11 @@
-// 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.
/*******************************************************************************
* Copyright (c) 2003, 2005 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
- *
+ *
* Contributors:
* IBM Corporation - Initial API and implementation
*******************************************************************************/
@@ -49,9 +49,9 @@
* 3) The class for an object may change. If so the new class should be able to read the old persistence info.
*
* We could ask the objects to serialize themselves into an ObjectOutputStream, DataOutputStream, or Hashtable. However all of these approaches fail to meet the second requirement.
- *
+ *
* Memento supports binary persistance with a version ID.
- *
+ *
* @author pc2@ecs.csus.edu
* @version $Id$
*/
@@ -73,6 +73,7 @@ private XMLMemento(Document doc, Element el) {
/**
* @see IMemento#createChild(String)
*/
+ @Override
public IMemento createChild(String type) {
Element child = factory.createElement(type);
element.appendChild(child);
@@ -82,6 +83,7 @@ public IMemento createChild(String type) {
/**
* @see IMemento#createChildNode(java.lang.String, java.lang.String)
*/
+ @Override
public IMemento createChildNode(String type, String value) {
Element child = factory.createElement(type);
@@ -93,6 +95,7 @@ public IMemento createChildNode(String type, String value) {
/**
* @see IMemento#createChild(String, String)
*/
+ @Override
public IMemento createChild(String type, String id) {
Element child = factory.createElement(type);
child.setAttribute(TAG_ID, id);
@@ -131,7 +134,7 @@ protected static XMLMemento createReadRoot(InputStream in) {
/**
* Answer a root memento for writing a document.
- *
+ *
* @param type
* a type
* @return a memento
@@ -151,6 +154,7 @@ public static XMLMemento createWriteRoot(String type) {
/*
* @see IMemento
*/
+ @Override
public IMemento getChild(String type) {
// Get the nodes.
NodeList nodes = element.getChildNodes();
@@ -177,6 +181,7 @@ public IMemento getChild(String type) {
/*
* @see IMemento
*/
+ @Override
public IMemento[] getChildren(String type) {
// Get the nodes.
NodeList nodes = element.getChildNodes();
@@ -201,14 +206,14 @@ public IMemento[] getChildren(String type) {
size = list.size();
IMemento[] results = new IMemento[size];
for (int x = 0; x < size; x++) {
- results[x] = new XMLMemento(factory, (Element) list.get(x));
+ results[x] = new XMLMemento(factory, list.get(x));
}
return results;
}
/**
* Return the contents of this memento as a byte array.
- *
+ *
* @return byte[]
* @throws IOException
* if anything goes wrong
@@ -221,7 +226,7 @@ public byte[] getContents() throws IOException {
/**
* Returns an input stream for writing to the disk with a local locale.
- *
+ *
* @return java.io.InputStream
* @throws IOException
* if anything goes wrong
@@ -235,6 +240,7 @@ public InputStream getInputStream() throws IOException {
/*
* @see IMemento
*/
+ @Override
public Float getFloat(String key) {
Attr attr = element.getAttributeNode(key);
if (attr == null) {
@@ -251,6 +257,24 @@ public Float getFloat(String key) {
/*
* @see IMemento
*/
+ @Override
+ public Double getDouble(String key) {
+ Attr attr = element.getAttributeNode(key);
+ if (attr == null) {
+ return null;
+ }
+ String strValue = attr.getValue();
+ try {
+ return new Double(strValue);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+
+ /*
+ * @see IMemento
+ */
+ @Override
public String getId() {
return element.getAttribute(TAG_ID);
}
@@ -258,6 +282,7 @@ public String getId() {
/*
* @see IMemento
*/
+ @Override
public String getName() {
return element.getNodeName();
}
@@ -265,6 +290,7 @@ public String getName() {
/*
* @see IMemento
*/
+ @Override
public Integer getInteger(String key) {
Attr attr = element.getAttributeNode(key);
if (attr == null) {
@@ -281,6 +307,7 @@ public Integer getInteger(String key) {
/*
* @see IMemento
*/
+ @Override
public Long getLong(String key) {
Attr attr = element.getAttributeNode(key);
if (attr == null) {
@@ -297,6 +324,7 @@ public Long getLong(String key) {
/*
* @see IMemento
*/
+ @Override
public String getString(String key) {
Attr attr = element.getAttributeNode(key);
if (attr == null) {
@@ -305,6 +333,7 @@ public String getString(String key) {
return attr.getValue();
}
+ @Override
public List getNames() {
NamedNodeMap map = element.getAttributes();
int size = map.getLength();
@@ -319,7 +348,7 @@ public List getNames() {
/**
* Loads a memento from the given filename.
- *
+ *
* @param filename
* java.lang.String
* @exception java.io.IOException
@@ -369,6 +398,15 @@ private void putElement(Element element2) {
/*
* @see IMemento
*/
+ @Override
+ public void putDouble(String key, double d) {
+ element.setAttribute(key, String.valueOf(d));
+ }
+
+ /*
+ * @see IMemento
+ */
+ @Override
public void putFloat(String key, float f) {
element.setAttribute(key, String.valueOf(f));
}
@@ -376,6 +414,7 @@ public void putFloat(String key, float f) {
/*
* @see IMemento
*/
+ @Override
public void putInteger(String key, int n) {
element.setAttribute(key, String.valueOf(n));
}
@@ -383,6 +422,7 @@ public void putInteger(String key, int n) {
/*
* @see IMemento
*/
+ @Override
public void putLong(String key, long n) {
element.setAttribute(key, Long.toString(n));
}
@@ -390,6 +430,7 @@ public void putLong(String key, long n) {
/*
* @see IMemento
*/
+ @Override
public void putMemento(IMemento memento) {
XMLMemento xmlMemento = (XMLMemento) memento;
putElement(xmlMemento.element);
@@ -398,6 +439,7 @@ public void putMemento(IMemento memento) {
/*
* @see IMemento
*/
+ @Override
public void putString(String key, String value) {
if (value == null) {
return;
@@ -407,7 +449,7 @@ public void putString(String key, String value) {
/**
* Save this Memento to a Writer.
- *
+ *
* @param os
* an output stream
* @throws IOException
@@ -416,10 +458,10 @@ public void putString(String key, String value) {
public void save(OutputStream os) throws IOException {
save(os, false);
}
-
+
/**
* Save this Memento to a Writer.
- *
+ *
* @param os
* an output stream
* @param omitXMLDeclaration
@@ -446,10 +488,10 @@ public void save(OutputStream os, boolean omitXMLDeclaration) throws IOException
throw (IOException) (new IOException().initCause(e));
}
}
-
+
/**
* Saves the memento to a String.
- *
+ *
* @exception java.io.IOException
*/
public String saveToString(boolean omitXMLDeclaration) throws IOException {
@@ -460,7 +502,7 @@ public String saveToString(boolean omitXMLDeclaration) throws IOException {
/**
* Saves the memento to a String.
- *
+ *
* @exception java.io.IOException
*/
public String saveToString() throws IOException {
@@ -469,7 +511,7 @@ public String saveToString() throws IOException {
/**
* Saves the memento to the given file.
- *
+ *
* @param filename
* java.lang.String
* @exception java.io.IOException
@@ -500,6 +542,7 @@ public void saveToFile(String filename) throws IOException {
/*
* @see IMemento#getBoolean(String)
*/
+ @Override
public Boolean getBoolean(String key) {
Attr attr = element.getAttributeNode(key);
if (attr == null) {
@@ -515,6 +558,7 @@ public Boolean getBoolean(String key) {
/*
* @see IMemento#putBoolean(String, boolean)
*/
+ @Override
public void putBoolean(String key, boolean value) {
element.setAttribute(key, Boolean.toString(value));
}
@@ -522,10 +566,11 @@ public void putBoolean(String key, boolean value) {
/**
* @see edu.csus.ecs.pc2.core.util.IMemento#getValue()
*/
+ @Override
public String getValue() {
NodeList list = element.getChildNodes();
- Node node = (Node) list.item(0);
+ Node node = list.item(0);
return node.getNodeValue();
}
}
diff --git a/src/edu/csus/ecs/pc2/graders/LegacyGrader.java b/src/edu/csus/ecs/pc2/graders/LegacyGrader.java
new file mode 100644
index 000000000..9ddb9d5ee
--- /dev/null
+++ b/src/edu/csus/ecs/pc2/graders/LegacyGrader.java
@@ -0,0 +1,350 @@
+// Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau.
+package edu.csus.ecs.pc2.graders;
+
+import java.util.ArrayList;
+import java.util.Scanner;
+
+/**
+ * This class performs the functions described in the Problem Package Format "Legacy" specification
+ * for the "Default Grader"
+ *
+ * @author John Buck
+ *
+ */
+public class LegacyGrader {
+
+ public static final String IGNORE_SAMPLE_FLAG = "ignore_sample";
+
+ public static final String ACCEPT_IF_ANY_ACCEPTED_FLAG = "accept_if_any_accepted";
+
+ public static final int GRADER_ERROR_BAD_FORMAT = 1;
+ public static final int GRADER_ERROR_BAD_SCORE = 2;
+ public static final int GRADER_ERROR_BAD_JUDGMENT = 3;
+ public static final int GRADER_ERROR_NO_TEST_CASES = 4;
+ public static final int GRADER_ERROR_BAD_WORST_CODE = 5;
+ public static final int GRADER_ERROR_BAD_FIRST_CODE = 6;
+ public static final int GRADER_ERROR_BAD_VERDICT_MODE = 7;
+ public static final int GRADER_ERROR_IGNORE_SAMPLE = 8;
+
+ enum VerdictMode {
+ worst_error,
+ first_error,
+ always_accept;
+ }
+
+ enum ScoringMode {
+ sum,
+ avg,
+ min,
+ max;
+ }
+
+ enum JudgmentCodes {
+ AC, // MUST be first (index 0)
+ RTE,
+ TLE,
+ WA;
+ }
+
+ private VerdictMode verdictMode = VerdictMode.worst_error;
+ private ScoringMode scoringMode = ScoringMode.sum;
+ private boolean acceptIfAnyAccepted = false;
+ private boolean ignoreSample = false;
+ private int graderError = 0;
+
+ /**
+ * See if the supplied string argument is a valid verdict mode.
+ *
+ * @param arg string to check for a verdict mode
+ * @return true if the argument supplied was accepted as the verdict mode, false otherwise.
+ */
+ private boolean checkVerdictMode(String arg) {
+ boolean result = false;
+
+ for(VerdictMode vm : VerdictMode.values()) {
+ if(arg.equals(vm.toString().toLowerCase())) {
+ verdictMode = vm;
+ result = true;
+ break;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * See if the supplied string argument is a valid scoring mode.
+ *
+ * @param arg string to check for a scoring mode
+ * @return true if the argument supplied was accepted as the scoring mode, false otherwise.
+ */
+ private boolean checkScoringMode(String arg) {
+ boolean result = false;
+
+ for(ScoringMode sm : ScoringMode.values()) {
+ if(arg.equals(sm.toString().toLowerCase())) {
+ scoringMode = sm;
+ result = true;
+ break;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * See if the supplied string argument is a flag.
+ *
+ * @param arg string to check for a flag
+ * @return true if the argument supplied was accepted as a valid flag, false otherwise.
+ */
+ private boolean checkFlags(String arg)
+ {
+ boolean result = false;
+
+ if(arg.equals(ACCEPT_IF_ANY_ACCEPTED_FLAG)) {
+ acceptIfAnyAccepted = true;
+ result = true;
+ } else if(arg.equals(IGNORE_SAMPLE_FLAG)) {
+ ignoreSample = true;
+ result = true;
+ }
+ return(result);
+ }
+
+ /**
+ * Process an array of string arguments for the grader.
+ * Note: This can be called directly if this package is included in a larger application
+ *
+ * @param args Array of arguments (see the Legacy Problem Package Format specification for Graders)
+ * @return true if there were no errors, false otherwise
+ */
+ public boolean parseArguments(String [] args) {
+ boolean result = true;
+
+ for(String arg : args ) {
+ if(!checkVerdictMode(arg) && !checkScoringMode(arg) && !checkFlags(arg)) {
+ result = false;
+ break;
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Read the stdin input for lines of test case judgments, create a list of them,
+ * and evaluate according the specification.
+ *
+ * @return 0 on success (this is the exit code for the program), non-zero indicates a judging error
+ */
+ public int processResults() {
+ Scanner scanner = new Scanner(System.in);
+ ArrayList testCaseResults = new ArrayList();
+
+ while(scanner.hasNextLine()) {
+ String line = scanner.nextLine().trim();
+ if(!line.isEmpty()) {
+ testCaseResults.add(line);
+ }
+ }
+ graderError = 0;
+ String result = gradeTestCases(testCaseResults);
+
+ if(result != null) {
+ System.out.println(result);
+ } else {
+ System.out.println("JE 0");
+ }
+ return(graderError);
+ }
+
+ public String gradeTestCases(ArrayList testCaseResults) {
+ int nLine = 0;
+ int idx;
+ boolean found;
+ double scoreSum = 0;
+ double scoreCount = 0;
+ double scoreMin = Double.POSITIVE_INFINITY;
+ double scoreMax = Double.NEGATIVE_INFINITY;
+ boolean [] sawJudgment = new boolean[JudgmentCodes.values().length];
+ boolean anyFailures = false;
+ boolean ignoreSampleGroup = ignoreSample;
+ String firstError = null;
+ String graderResult = null;
+
+ graderError = 0;
+ for(String line : testCaseResults) {
+ nLine++;
+ /*
+ * A little explanation here about ignoring samples.
+ * The Legacy Grader specification says:
+ * "Must only be used on the root level. The first sub-result (sample)
+ * will be ignored, the second sub-result (secret) will be used,
+ * both verdict and score.
+ * If the ignore_sample command line flag was supplied, it implies the group
+ * being graded is the root level, as such, there will be exactly 2 sub-groups
+ * at the level, sample and secret (in that order - lexicographically). Therefore,
+ * the first group (sample) will simply be ignored (skipped). Grading will procede
+ * with the next line, which will be secret.
+ */
+ if(ignoreSampleGroup) {
+ // Ignore sample group which is the first one. There better be exactly one more
+ // group in the list, or it's a judging error.
+ if(testCaseResults.size() != 2) {
+ graderError = GRADER_ERROR_IGNORE_SAMPLE;
+ break;
+ }
+ ignoreSampleGroup = false;
+ continue;
+ }
+ String [] values = line.trim().split("\\s+");
+ if(values.length != 2) {
+ System.err.println("LegacyGrader: invalid input test case " + nLine);
+ graderError = GRADER_ERROR_BAD_FORMAT;
+ break;
+ }
+ // be generous and accept upper or lower case for judgment
+ String code = values[0].toUpperCase();
+ double score = 0;
+ try {
+ score = Double.parseDouble(values[1]);
+ } catch(NumberFormatException e) {
+ System.err.println("LegacyGrader: invalid score for test case " + nLine);
+ graderError = GRADER_ERROR_BAD_SCORE;
+ break;
+ }
+ found = false;
+ // We now have the possible code and the score
+ for(JudgmentCodes jcode : JudgmentCodes.values()) {
+ if(code.equals(jcode.toString())) {
+ idx = jcode.ordinal();
+ // Tally score - we do this even for reject cases(!)
+ scoreSum += score;
+ scoreCount += 1;
+
+ // keep track of min and max score
+ if(score < scoreMin) {
+ scoreMin = score;
+ }
+ if(score > scoreMax) {
+ scoreMax = score;
+ }
+
+ // check for first failure
+ if(idx > 0) {
+ if(firstError == null) {
+ firstError = jcode.toString();
+ }
+ anyFailures = true;
+ }
+ sawJudgment[idx] = true;
+ found = true;
+ break;
+ }
+ }
+ if(!found) {
+ System.err.println("LegacyGrader: Unknown judgment code " + code + " for test case " + nLine);
+ graderError = GRADER_ERROR_BAD_JUDGMENT;
+ break;
+ }
+ if(graderError != 0) {
+ break;
+ }
+ }
+ // Only calculate judgment if there were no errors
+ if(graderError == 0) {
+ // Check for no failures or always accept mode or optional flag "any" accepted case
+ if(!anyFailures || verdictMode == VerdictMode.always_accept || (acceptIfAnyAccepted && scoreCount > 0)) {
+ if(scoreCount == 0) {
+ // this there were no judgments in the input
+ System.err.println("LegacyGrader: No judgments in the input.");
+ graderError = GRADER_ERROR_NO_TEST_CASES;
+ } else {
+ double score = 0;
+ // In this case, we return a score
+ switch(scoringMode) {
+ case sum: // add'm up
+ score = scoreSum;
+ break;
+ case avg: // calculate mean
+ score = scoreSum / scoreCount;
+ break;
+ case min: // the smallest score
+ score = scoreMin;
+ break;
+ case max: // the biggest score
+ score = scoreMax;
+ break;
+ }
+ graderResult = JudgmentCodes.AC.toString() + " " + score;
+ }
+ } else {
+ // determine non-accepted judgment
+ // All cases should either print the correct output to stdout, or print
+ // an error to stderr and set graderError to a non-zero value.
+ switch(verdictMode) {
+ case worst_error:
+ found = false;
+ for(JudgmentCodes jcode : JudgmentCodes.values()) {
+ idx = jcode.ordinal();
+ if(idx > 0 && sawJudgment[idx]) {
+ System.out.println(jcode.toString() + " 0");
+ found = true;
+ break;
+ }
+ }
+ if(!found) {
+ // Uhm. This is extremely bad, Tammy, since we KNOW anyFailures must be true
+ System.err.println("LegacyGrader: FATAL error - can not find judgment code for worst_error mode.");
+ graderError = GRADER_ERROR_BAD_WORST_CODE;
+ }
+ break;
+
+ case first_error:
+ if(firstError == null) {
+ // Uhm. This is extremely bad took Tammy, since we know anyFailures must be true
+ 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");
+ }
+ break;
+
+ default:
+ // Very bad, Tammy. This can't happen because alwaysAccept was handled above
+ System.err.println("LegacyGrader: FATAL error - invalid verdict mode " + verdictMode.toString());
+ graderError = GRADER_ERROR_BAD_VERDICT_MODE;
+ break;
+ }
+ }
+ }
+ // 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;
+ }
+
+ /**
+ * Returns the last error the grader saw, or 0 if no errors.
+ * This may be useful if the getTestCases() method returns null.
+ *
+ * @return the last grader error
+ */
+ int getGraderError() {
+ return graderError;
+ }
+
+ /**
+ * @param args - optional combination of a VerdictMode, ScoringMode and Flag(s)
+ */
+ public static void main(String[] args) {
+ LegacyGrader grader = new LegacyGrader();
+ int exitCode = 0;
+
+ if(!grader.parseArguments(args)) {
+ System.err.println("LegacyGrader: Unrecognized option supplied.");
+ exitCode = 1;
+ } else {
+ exitCode = grader.processResults();
+ }
+ System.exit(exitCode);
+ }
+
+}
diff --git a/src/edu/csus/ecs/pc2/imports/ccs/ContestImportUtilities.java b/src/edu/csus/ecs/pc2/imports/ccs/ContestImportUtilities.java
new file mode 100644
index 000000000..495b3413b
--- /dev/null
+++ b/src/edu/csus/ecs/pc2/imports/ccs/ContestImportUtilities.java
@@ -0,0 +1,308 @@
+// Copyright (C) 1989-2024 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau.
+package edu.csus.ecs.pc2.imports.ccs;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Map;
+
+import org.yaml.snakeyaml.Yaml;
+import org.yaml.snakeyaml.error.Mark;
+import org.yaml.snakeyaml.error.MarkedYAMLException;
+
+import edu.csus.ecs.pc2.core.StringUtilities;
+import edu.csus.ecs.pc2.core.exception.YamlLoadException;
+
+/**
+ * Utilities to help with importing contest data
+ *
+ * @author John Buck, PC^2 Team, pc2@ecs.csus.edu
+ */
+public class ContestImportUtilities {
+ /**
+ * Get boolean value for input key in map.
+ *
+ * Returns defaultVaue if no entry matches key.
+ *
+ * @param content
+ * @param key
+ * @param defaultValue
+ * @return defaultValue or value from item in map.
+ */
+ public static boolean fetchBooleanValue(Map content, String key, boolean defaultValue) {
+ Object object = content.get(key);
+ Boolean value = false;
+ if (object == null) {
+ return defaultValue;
+ } else if (object instanceof Boolean) {
+ value = (Boolean) content.get(key);
+ } else if (object instanceof String) {
+ value = StringUtilities.getBooleanValue((String) content.get(key), defaultValue);
+ }
+ return value;
+ }
+
+ public static Object fetchObjectValue(Map content, String key) {
+ if (content == null) {
+ return null;
+ }
+ Object value = content.get(key);
+ return value;
+ }
+
+ @SuppressWarnings("unused")
+ private boolean fetchBooleanValue(Map content, String key) {
+ return fetchBooleanValue(content, key, false);
+ }
+
+ /**
+ * Fetch value from a map.
+ *
+ * @param content
+ * @param key
+ * @return null if content does not contain a value for the key, else the value for the key.
+ */
+ public static String fetchValue(Map content, String key) {
+ if (content == null) {
+ return null;
+ }
+ Object value = content.get(key);
+ if (value == null) {
+ return null;
+ } else if (value instanceof String) {
+ return (String) content.get(key);
+ } else {
+ return content.get(key).toString();
+ }
+ }
+
+ public static String fetchValue(Map content, String key, String defaultValue) {
+ if (content == null) {
+ return null;
+ }
+ Object value = content.get(key);
+ if (value == null) {
+ return defaultValue;
+ } else if (value instanceof String) {
+ return (String) content.get(key);
+ } else {
+ return content.get(key).toString();
+ }
+ }
+
+
+ public static boolean isValuePresent(Map content, String key) {
+ if (content == null) {
+ return false;
+ }
+ Object value = content.get(key);
+ return value != null;
+ }
+
+ public static String fetchValueDefault(Map map, String key, String defaultValue) {
+ String value = fetchValue(map, key);
+ if (value == null) {
+ value = defaultValue;
+ }
+ return value;
+ }
+
+ public static Integer fetchIntValue(Map map, String key, int defaultValue) {
+ Integer value = null;
+ if (map != null) {
+ value = (Integer) map.get(key);
+ }
+ if (value != null) {
+ try {
+ return value;
+ } catch (Exception e) {
+ syntaxError("Expecting number after " + key + ": field, found '" + value + "'");
+ }
+ }
+ return defaultValue;
+ }
+
+ public static Long fetchLongValue(Map map, String key, long defaultValue) {
+ Long value = null;
+ if (map != null) {
+ value = (Long) map.get(key);
+ }
+ if (value != null) {
+ try {
+ return value;
+ } catch (Exception e) {
+ syntaxError("Expecting number after " + key + ": field, found '" + value + "'");
+ }
+ }
+ return defaultValue;
+ }
+
+ public static Double fetchDoubleValue(Map map, String key) {
+ Double value = null;
+ if (map != null) {
+ Object oVal = map.get(key);
+ if(oVal instanceof Integer) {
+ value = Double.valueOf(((Integer)oVal).doubleValue());
+ } else {
+ value = (Double) oVal;
+ }
+ if (value != null) {
+ try {
+ return value;
+ } catch (Exception e) {
+ syntaxError("Expecting double after " + key + ": field, found '" + value + "'");
+ }
+ }
+ }
+ return value;
+ }
+
+
+ public static Integer fetchIntValue(Map map, String key) {
+ if (map == null) {
+ // SOMEDAY figure out why map would every be null
+ return null;
+ }
+ Integer value = (Integer) map.get(key);
+ if (value != null) {
+ try {
+ return value;
+ } catch (Exception e) {
+ syntaxError("Expecting number after " + key + ": field, found '" + value + "'");
+ }
+ }
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Map fetchMap(Map content, String key) {
+ Object object = content.get(key);
+ if (object != null) {
+ if (object instanceof Map) {
+ return (Map) content.get(key);
+ } else {
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ public static String[] fetchStringList(String[] yamlLines, String key) {
+ Map content = loadYaml(null, yamlLines);
+ ArrayList list = fetchList(content, key);
+ if (list == null) {
+ return new String[0];
+ } else {
+ return (String[]) list.toArray(new String[list.size()]);
+ }
+ }
+
+
+ public static Map getContent(String filename) {
+ return(loadYaml(filename));
+ }
+
+ @SuppressWarnings("rawtypes")
+ public static ArrayList fetchList(Map content, String key) {
+ return (ArrayList) content.get(key);
+ }
+
+ public static String fetchValue(File file, String key) {
+ Map content = getContent(file.getAbsolutePath());
+ return (String) content.get(key);
+ }
+
+ public static String fetchFileValue(String filename, String key) {
+ return fetchValue(new File(filename), key);
+ }
+
+ public static void syntaxError(String string) {
+ YamlLoadException exception = new YamlLoadException("Syntax error: " + string);
+ throw exception;
+ }
+ @SuppressWarnings("unchecked")
+ public static Map loadYaml(String filename) {
+ try {
+ Yaml yaml = new Yaml();
+ return (Map) yaml.load(new FileInputStream(filename));
+ } catch (MarkedYAMLException e) {
+ throw new YamlLoadException(getSnakeParserDetails(e), e, filename);
+ } catch (FileNotFoundException e) {
+ throw new YamlLoadException("File not found " + filename, e, filename);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ public static Map loadYaml(String filename, String[] yamlLines) {
+ try {
+ Yaml yaml = new Yaml();
+ String fullString = StringUtilities.join("\n", yamlLines);
+ InputStream stream = new ByteArrayInputStream(fullString.getBytes(StandardCharsets.UTF_8));
+ return (Map) yaml.load(stream);
+ } catch (MarkedYAMLException e) {
+ throw new YamlLoadException(getSnakeParserDetails(e), e, filename);
+ }
+ }
+
+ /**
+ * read directory and return list of files with given extension
+ * @param directoryName to search
+ * @param extension to look for
+ * @return array of filenames
+ */
+ public static ArrayList getTestCaseFileNames(String directoryName) {
+
+ ArrayList list = new ArrayList();
+ File dir = new File(directoryName);
+
+ String[] entries = dir.list();
+ HashSet fileNames = new HashSet();
+ String ansFile;
+
+ if (entries != null) {
+ Arrays.sort(entries);
+
+ for (String name : entries) {
+ fileNames.add(name);
+ }
+ for (String name : entries) {
+ if (name.endsWith(TestCaseInfo.TEST_CASE_INPUT_EXTENSION)) {
+ // the answer file better be there
+ ansFile = name.replaceAll(TestCaseInfo.TEST_CASE_INPUT_EXTENSION + "$", TestCaseInfo.TEST_CASE_ANSWER_EXTENSION);
+ if(!fileNames.contains(ansFile)) {
+ throw new YamlLoadException("Missing answer file " + ansFile + " for input file " + name);
+ }
+ list.add(new TestCaseInfo(name, ansFile, null));
+ }
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Create a simple string with parse info.
+ *
+ * @param markedYAMLException
+ * @return
+ */
+ public static String getSnakeParserDetails(MarkedYAMLException markedYAMLException) {
+
+ Mark mark = markedYAMLException.getProblemMark();
+
+ int lineNumber = mark.getLine() + 1; // starts at zero
+ int columnNumber = mark.getColumn() + 1; // starts at zero
+
+ return "Parse error at line=" + lineNumber + " column=" + columnNumber + " message=" + markedYAMLException.getProblem();
+
+ }
+
+
+}
diff --git a/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java b/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java
index 94a58d810..2f487a599 100644
--- a/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java
+++ b/src/edu/csus/ecs/pc2/imports/ccs/ContestSnakeYAMLLoader.java
@@ -1,13 +1,9 @@
// Copyright (C) 1989-2025 PC2 Development Team: John Clevenger, Douglas Lane, Samir Ashoo, and Troy Boudreau.
package edu.csus.ecs.pc2.imports.ccs;
-import java.io.ByteArrayInputStream;
import java.io.File;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
@@ -23,10 +19,6 @@
import javax.xml.bind.DatatypeConverter;
-import org.yaml.snakeyaml.Yaml;
-import org.yaml.snakeyaml.error.Mark;
-import org.yaml.snakeyaml.error.MarkedYAMLException;
-
import edu.csus.ecs.pc2.core.Constants;
import edu.csus.ecs.pc2.core.JudgementLoader;
import edu.csus.ecs.pc2.core.StringUtilities;
@@ -76,58 +68,13 @@
*/
public class ContestSnakeYAMLLoader implements IContestLoader {
- /**
- * Full content of yaml file.
- */
- private Map fullYamlContent = null;
+ private String judgesCDPDataPath = null;
/**
* Load Problem Data File Contents
*/
private boolean loadProblemDataFiles = true;
- @SuppressWarnings("unchecked")
- public Map loadYaml(String filename) {
- try {
- Yaml yaml = new Yaml();
- return (Map) yaml.load(new FileInputStream(filename));
- } catch (MarkedYAMLException e) {
- throw new YamlLoadException(getSnakeParserDetails(e), e, filename);
- } catch (FileNotFoundException e) {
- throw new YamlLoadException("File not found " + filename, e, filename);
- }
- }
-
- @SuppressWarnings("unchecked")
- public Map loadYaml(String filename, String[] yamlLines) {
- try {
- Yaml yaml = new Yaml();
- String fullString = StringUtilities.join("\n", yamlLines);
- InputStream stream = new ByteArrayInputStream(fullString.getBytes(StandardCharsets.UTF_8));
- return (Map) yaml.load(stream);
- } catch (MarkedYAMLException e) {
- throw new YamlLoadException(getSnakeParserDetails(e), e, filename);
- }
- }
-
- /**
- * Create a simple string with parse info.
- *
- * @param markedYAMLException
- * @return
- */
-
- String getSnakeParserDetails(MarkedYAMLException markedYAMLException) {
-
- Mark mark = markedYAMLException.getProblemMark();
-
- int lineNumber = mark.getLine() + 1; // starts at zero
- int columnNumber = mark.getColumn() + 1; // starts at zero
-
- return "Parse error at line=" + lineNumber + " column=" + columnNumber + " message=" + markedYAMLException.getProblem();
-
- }
-
@Override
public IInternalContest fromYaml(IInternalContest contest, String directoryName) {
return fromYaml(contest, directoryName, true);
@@ -219,34 +166,20 @@ public String getContestTitle(String contestYamlFilename) throws IOException {
File contestYaml = new File(contestYamlFilename);
// Try CLICS name first. Fun fact: CLICS_CONTEST_NAME == CONTEST_NAME_KEY, but may not someday
- String contestTitle = fetchValue(contestYaml, IContestLoader.CLICS_CONTEST_NAME);
+ String contestTitle = ContestImportUtilities.fetchValue(contestYaml, IContestLoader.CLICS_CONTEST_NAME);
// only if the CLICS name isn't there do we try the old one. non-null means it is there.
if(contestTitle == null) {
- contestTitle = fetchValue(contestYaml, IContestLoader.CONTEST_NAME_KEY);
+ contestTitle = ContestImportUtilities.fetchValue(contestYaml, IContestLoader.CONTEST_NAME_KEY);
}
return(contestTitle);
}
- protected String fetchValue(File file, String key) {
- Map content = getContent(file.getAbsolutePath());
- return (String) content.get(key);
- }
-
- private Map getContent(String filename) {
- if (fullYamlContent == null) {
- fullYamlContent = loadYaml(filename);
- }
-
- return fullYamlContent;
- }
-
@Override
public String getJudgesCDPBasePath(String contestYamlFilename) throws IOException {
- return fetchFileValue(contestYamlFilename, JUDGE_CONFIG_PATH_KEY);
- }
-
- private String fetchFileValue(String filename, String key) {
- return fetchValue(new File(filename), key);
+ if(judgesCDPDataPath == null) {
+ judgesCDPDataPath = ContestImportUtilities.fetchFileValue(contestYamlFilename, JUDGE_CONFIG_PATH_KEY);
+ }
+ return(judgesCDPDataPath);
}
/**
@@ -424,6 +357,11 @@ private void setContestStartDateTime(IInternalContest contest, Date date) {
contestInformation.setAutoStartContest(isBeforeNow(date));
}
+ private void setContestScoreboardType(IInternalContest contest, String type) {
+ ContestInformation contestInformation = contest.getContestInformation();
+ contestInformation.setScoreboardType(type);
+ }
+
/**
* Input date before now, aka current date/time.
*
@@ -444,7 +382,7 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S
String contestFileName = getContestYamlFilename(directoryName);
- Map content = loadYaml(contestFileName, yamlLines);
+ Map content = ContestImportUtilities.loadYaml(contestFileName, yamlLines);
if (content == null) {
return contest;
@@ -452,20 +390,20 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S
setTitle(contest, null);
- String contestTitle = fetchValue(content, CONTEST_NAME_KEY);
+ String contestTitle = ContestImportUtilities.fetchValue(content, CONTEST_NAME_KEY);
if (contestTitle != null) {
setTitle(contest, contestTitle);
}
- boolean ccsTestMode = fetchBooleanValue(content, CCS_TEST_MODE, false);
+ boolean ccsTestMode = ContestImportUtilities.fetchBooleanValue(content, CCS_TEST_MODE, false);
if (ccsTestMode) {
setCcsTestMode(contest, ccsTestMode);
}
- boolean loadSamples = fetchBooleanValue(content, LOAD_SAMPLE_JUDGES_DATA, true);
+ boolean loadSamples = ContestImportUtilities.fetchBooleanValue(content, LOAD_SAMPLE_JUDGES_DATA, true);
setLoadSampleJudgesData(contest, loadSamples);
- boolean stopOnFirstFail = fetchBooleanValue(content, STOP_ON_FIRST_FAILED_TEST_CASE_KEY, false);
+ boolean stopOnFirstFail = ContestImportUtilities.fetchBooleanValue(content, STOP_ON_FIRST_FAILED_TEST_CASE_KEY, false);
setStopOnFirstFailedTestCase (contest, stopOnFirstFail);
/**
@@ -475,65 +413,65 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S
ContestInformation contestInformation = getContestInformation(contest);
//set allow-multiple-team-logins mode
- boolean allowMultipleTeamLogins = fetchBooleanValue(content, ALLOW_MULTIPLE_TEAM_LOGINS_KEY, contestInformation.isAllowMultipleLoginsPerTeam());
+ boolean allowMultipleTeamLogins = ContestImportUtilities.fetchBooleanValue(content, ALLOW_MULTIPLE_TEAM_LOGINS_KEY, contestInformation.isAllowMultipleLoginsPerTeam());
contestInformation.setAllowMultipleLoginsPerTeam(allowMultipleTeamLogins);
// Load team scoreboard string (the one with variables)
- String teamScoreboadDisplayString = fetchValue(content, TEAM_SCOREBOARD_DISPLAY_FORMAT_STRING, contestInformation.getTeamScoreboardDisplayFormat());
+ String teamScoreboadDisplayString = ContestImportUtilities.fetchValue(content, TEAM_SCOREBOARD_DISPLAY_FORMAT_STRING, contestInformation.getTeamScoreboardDisplayFormat());
contestInformation.setTeamScoreboardDisplayFormat(teamScoreboadDisplayString);
// enable shadow mode
- boolean shadowMode = fetchBooleanValue(content, SHADOW_MODE_KEY, contestInformation.isShadowMode());
+ boolean shadowMode = ContestImportUtilities.fetchBooleanValue(content, SHADOW_MODE_KEY, contestInformation.isShadowMode());
contestInformation.setShadowMode(shadowMode);
- String altAccountsLoadFilename = fetchValue(content, LOAD_ACCOUNTS_FILE_KEY, null);
+ String altAccountsLoadFilename = ContestImportUtilities.fetchValue(content, LOAD_ACCOUNTS_FILE_KEY, null);
contestInformation.setOverrideLoadAccountsFilename(altAccountsLoadFilename);
// base URL for CCS REST service
- String ccsUrl= fetchValue(content, CCS_URL_KEY, contestInformation.getPrimaryCCS_URL());
+ String ccsUrl= ContestImportUtilities.fetchValue(content, CCS_URL_KEY, contestInformation.getPrimaryCCS_URL());
contestInformation.setPrimaryCCS_URL(ccsUrl);
// CCS REST login
- String ccsLogin = fetchValue(content, CCS_LOGIN_KEY, contestInformation.getPrimaryCCS_user_login());
+ String ccsLogin = ContestImportUtilities.fetchValue(content, CCS_LOGIN_KEY, contestInformation.getPrimaryCCS_user_login());
contestInformation.setPrimaryCCS_user_login(ccsLogin);
// CCS REST password
- String ccsPassoword = fetchValue(content, CCS_PASSWORD_KEY, contestInformation.getPrimaryCCS_user_pw());
+ String ccsPassoword = ContestImportUtilities.fetchValue(content, CCS_PASSWORD_KEY, contestInformation.getPrimaryCCS_user_pw());
contestInformation.setPrimaryCCS_user_pw(ccsPassoword);
- String lastEventId = fetchValue(content, CCS_LAST_EVENT_ID_KEY, contestInformation.getLastShadowEventID());
+ String lastEventId = ContestImportUtilities.fetchValue(content, CCS_LAST_EVENT_ID_KEY, contestInformation.getLastShadowEventID());
contestInformation.setLastShadowEventID(lastEventId);
- String executeDir = fetchValue(content, EXECUTE_FOLDER, contestInformation.getExecuteFolder());
+ String executeDir = ContestImportUtilities.fetchValue(content, EXECUTE_FOLDER, contestInformation.getExecuteFolder());
contestInformation.setExecuteFolder(executeDir);
// save ContesInformation to model
contest.updateContestInformation(contestInformation);
- String judgeCDPath = fetchValue(content, JUDGE_CONFIG_PATH_KEY);
+ String judgeCDPath = ContestImportUtilities.fetchValue(content, JUDGE_CONFIG_PATH_KEY);
if (judgeCDPath != null) {
setCDPPath(contest, judgeCDPath);
} else {
setCDPPath(contest, directoryName);
}
- Integer defaultTimeout = fetchIntValue(content, TIMEOUT_KEY, DEFAULT_TIME_OUT);
+ Integer defaultTimeout = ContestImportUtilities.fetchIntValue(content, TIMEOUT_KEY, DEFAULT_TIME_OUT);
int currentGlobalMemoryLimit = getMemoryLimitMB(contest);
- Integer globalMemoryLimit = fetchIntValue(content, MEMORY_LIMIT_IN_MEG_KEY, currentGlobalMemoryLimit);
+ Integer globalMemoryLimit = ContestImportUtilities.fetchIntValue(content, MEMORY_LIMIT_IN_MEG_KEY, currentGlobalMemoryLimit);
if(currentGlobalMemoryLimit != globalMemoryLimit) {
setMemoryLimitMB(contest, globalMemoryLimit);
}
int currentSandboxGraceTime = getSandboxGraceTimeSecs(contest);
- Integer sandboxGraceTime = fetchIntValue(content, SANDBOX_GRACE_TIME, currentSandboxGraceTime);
+ Integer sandboxGraceTime = ContestImportUtilities.fetchIntValue(content, SANDBOX_GRACE_TIME, currentSandboxGraceTime);
if(currentSandboxGraceTime != sandboxGraceTime) {
setSandboxGraceTimeSecs(contest, sandboxGraceTime);
}
int currentSandboxIntMult = getSandboxInteractiveTimeMultiplier(contest);
- Integer sandboxIntMult = fetchIntValue(content, SANDBOX_INTERACTIVE_GRACE_MULTIPLIER, currentSandboxIntMult);
+ Integer sandboxIntMult = ContestImportUtilities.fetchIntValue(content, SANDBOX_INTERACTIVE_GRACE_MULTIPLIER, currentSandboxIntMult);
if(currentSandboxIntMult != sandboxIntMult) {
setSandboxInteractiveTimeMultiplier(contest, sandboxIntMult);
}
@@ -545,9 +483,9 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S
}
}
- loadDataFileContents = fetchBooleanValue(content, PROBLEM_LOAD_DATA_FILES_KEY, loadDataFileContents);
+ loadDataFileContents = ContestImportUtilities.fetchBooleanValue(content, PROBLEM_LOAD_DATA_FILES_KEY, loadDataFileContents);
- String shortContestName = fetchValue(content, CLICS_CONTEST_ID);
+ String shortContestName = ContestImportUtilities.fetchValue(content, CLICS_CONTEST_ID);
// Check if id is CLICS compliant
if (!StringUtilities.isEmpty(shortContestName)) {
@@ -563,13 +501,13 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S
}
} else {
// only if CLICS id is not present do we try older key `short-name`
- shortContestName = fetchValue(content, SHORT_NAME_KEY);
+ shortContestName = ContestImportUtilities.fetchValue(content, SHORT_NAME_KEY);
shortContestName = StringUtilities.makeStringCLICSCompliant(shortContestName);
}
// only if both CLICS id and `short-name` is not present do we try the key `name`
if (StringUtilities.isEmpty(shortContestName)) {
- shortContestName = fetchValue(content, CLICS_CONTEST_NAME);
+ shortContestName = ContestImportUtilities.fetchValue(content, CLICS_CONTEST_NAME);
shortContestName = StringUtilities.makeStringCLICSCompliant(shortContestName);
}
// only set short name if string is present AND not empty
@@ -579,25 +517,25 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S
StaticLog.warning("None of CLICS id, name and short-name is present. Contest Identifier will be set as Default-{:random_number}.");
}
- if (null != fetchValue(content, AUTO_STOP_CLOCK_AT_END_KEY)) {
+ if (null != ContestImportUtilities.fetchValue(content, AUTO_STOP_CLOCK_AT_END_KEY)) {
// only set value if key present
- boolean autoStopClockAtEnd = fetchBooleanValue(content, AUTO_STOP_CLOCK_AT_END_KEY, false);
+ boolean autoStopClockAtEnd = ContestImportUtilities.fetchBooleanValue(content, AUTO_STOP_CLOCK_AT_END_KEY, false);
setAutoStopClockAtEnd(contest, autoStopClockAtEnd);
}
- String contestLength = fetchValue(content, CLICS_CONTEST_DURATION);
+ String contestLength = ContestImportUtilities.fetchValue(content, CLICS_CONTEST_DURATION);
// if CLICS duration not present, try old duration.
// note as of CLICS spec 2022-07, the CLICS key is the same as the old one
// we leave the old test here in case the CLICS key changes at some point.
if(contestLength == null) {
- contestLength = fetchValue(content, CONTEST_DURATION_KEY);
+ contestLength = ContestImportUtilities.fetchValue(content, CONTEST_DURATION_KEY);
}
if (contestLength != null) {
setContestLength(contest, contestLength);
}
- boolean isRunning = fetchBooleanValue(content, "running", false);
+ boolean isRunning = ContestImportUtilities.fetchBooleanValue(content, "running", false);
if (isRunning){
ContestTime time = contest.getContestTime();
if (time == null) {
@@ -611,16 +549,16 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S
// There are several ways to specify the freeze length. We try them here
// in order of preference: CLICS, old, new (does that make sense? shouldn't
// new be tried before old? -- JB)
- String scoreboardFreezeTime = fetchValue(content, CLICS_CONTEST_FREEZE_DURATION);
+ String scoreboardFreezeTime = ContestImportUtilities.fetchValue(content, CLICS_CONTEST_FREEZE_DURATION);
// This is absolutely ridiculous, but backward compatible *sigh*
if(scoreboardFreezeTime == null) {
// Old yaml name
- scoreboardFreezeTime = fetchValue(content, SCOREBOARD_FREEZE_KEY);
+ scoreboardFreezeTime = ContestImportUtilities.fetchValue(content, SCOREBOARD_FREEZE_KEY);
if(scoreboardFreezeTime == null) {
// New yaml name
- scoreboardFreezeTime = fetchValue(content, SCOREBOARD_FREEZE_LENGTH_KEY);
+ scoreboardFreezeTime = ContestImportUtilities.fetchValue(content, SCOREBOARD_FREEZE_LENGTH_KEY);
}
}
// Only set time if not null or empty
@@ -628,10 +566,10 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S
setScoreboardFreezeTime(contest, scoreboardFreezeTime);
}
- Object startTimeObject = fetchObjectValue(content, CLICS_CONTEST_START_TIME);
+ Object startTimeObject = ContestImportUtilities.fetchObjectValue(content, CLICS_CONTEST_START_TIME);
// if clics start time not present(!), try the old one
if(startTimeObject == null) {
- startTimeObject = fetchObjectValue(content, CONTEST_START_TIME_KEY);
+ startTimeObject = ContestImportUtilities.fetchObjectValue(content, CONTEST_START_TIME_KEY);
}
Date date = null;
@@ -639,10 +577,10 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S
setContestStartDateTime(contest, (Date) startTimeObject);
} else {
- String startTime = fetchValue(content, CLICS_CONTEST_START_TIME);
+ String startTime = ContestImportUtilities.fetchValue(content, CLICS_CONTEST_START_TIME);
// only if CLICS start time is NOT there do we try the old one
if(startTime == null) {
- startTime = fetchValue(content, CONTEST_START_TIME_KEY);
+ startTime = ContestImportUtilities.fetchValue(content, CONTEST_START_TIME_KEY);
}
if (startTime != null) {
@@ -680,11 +618,20 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S
}
// If the contest type is present in contest.yaml, verify it
- String scoreType = fetchValue(content, CLICS_CONTEST_SCOREBOARD_TYPE);
- if(scoreType != null && !scoreType.equals("pass-fail")) {
- throw new YamlLoadException("Invalid " + CLICS_CONTEST_SCOREBOARD_TYPE + ": " + scoreType + ", expected pass-fail");
+ String scoreType = ContestImportUtilities.fetchValue(content, CLICS_CONTEST_SCOREBOARD_TYPE);
+ 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);
+ }
+ setContestScoreboardType(contest, scoreType);
}
- Object privatehtmlOutputDirectory = fetchObjectValue(content, OUTPUT_PRIVATE_SCORE_DIR_KEY);
+
+ Object privatehtmlOutputDirectory = ContestImportUtilities.fetchObjectValue(content, OUTPUT_PRIVATE_SCORE_DIR_KEY);
if (privatehtmlOutputDirectory != null) {
if (privatehtmlOutputDirectory instanceof String) {
setScoringPropertyValue(contest, DefaultScoringAlgorithm.JUDGE_OUTPUT_DIR, (String) privatehtmlOutputDirectory);
@@ -693,7 +640,7 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S
}
}
- Object publichtmlOutputDirectory = fetchObjectValue(content, OUTPUT_PUBLIC_SCORE_DIR_KEY);
+ Object publichtmlOutputDirectory = ContestImportUtilities.fetchObjectValue(content, OUTPUT_PUBLIC_SCORE_DIR_KEY);
if (publichtmlOutputDirectory != null) {
if (publichtmlOutputDirectory instanceof String) {
setScoringPropertyValue(contest, DefaultScoringAlgorithm.PUBLIC_OUTPUT_DIR, (String) publichtmlOutputDirectory);
@@ -702,7 +649,7 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S
}
}
- Object maxOutputSize = fetchObjectValue(content, MAX_OUTPUT_SIZE_K_KEY);
+ Object maxOutputSize = ContestImportUtilities.fetchObjectValue(content, MAX_OUTPUT_SIZE_K_KEY);
if (maxOutputSize != null) {
if (maxOutputSize instanceof Integer) {
@@ -722,9 +669,9 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S
contest.addLanguage(language);
}
- String defaultValidatorCommandLine = fetchValue(content, DEFAULT_VALIDATOR_KEY);
+ String defaultValidatorCommandLine = ContestImportUtilities.fetchValue(content, DEFAULT_VALIDATOR_KEY);
- String overrideValidatorCommandLine = fetchValue(content, OVERRIDE_VALIDATOR_KEY);
+ String overrideValidatorCommandLine = ContestImportUtilities.fetchValue(content, OVERRIDE_VALIDATOR_KEY);
if (overrideValidatorCommandLine == null) {
// if no override defined, then maybe use mtsv
@@ -739,7 +686,7 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S
}
boolean overrideUsePc2Validator = false;
- String usingValidator = fetchValue(content, IContestLoader.USING_PC2_VALIDATOR);
+ String usingValidator = ContestImportUtilities.fetchValue(content, IContestLoader.USING_PC2_VALIDATOR);
if (usingValidator != null && usingValidator.equalsIgnoreCase("true")) {
overrideUsePc2Validator = true;
@@ -753,9 +700,9 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S
@SuppressWarnings("unchecked")
LinkedHashMap judgingTypeContent = (LinkedHashMap) content.get(JUDGING_TYPE_KEY);
if (judgingTypeContent != null) {
- manualReviewOverride = fetchBooleanValue(judgingTypeContent, MANUAL_REVIEW_KEY, false);
+ manualReviewOverride = ContestImportUtilities.fetchBooleanValue(judgingTypeContent, MANUAL_REVIEW_KEY, false);
} else {
- manualReviewOverride = fetchBooleanValue(content, MANUAL_REVIEW_KEY, false);
+ manualReviewOverride = ContestImportUtilities.fetchBooleanValue(content, MANUAL_REVIEW_KEY, false);
}
//get the current default global output size for the contest so getProblems() can use it if no
@@ -812,17 +759,17 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S
Account[] accounts = getAccounts(yamlLines);
- Map passwordYamlMap = fetchMap(content, "passwords");
+ Map passwordYamlMap = ContestImportUtilities.fetchMap(content, "passwords");
if (passwordYamlMap != null) {
- String passTypeString = fetchValueDefault(passwordYamlMap, "type", PasswordType2.LETTERS_AND_DIGITS.toString());
+ String passTypeString = ContestImportUtilities.fetchValueDefault(passwordYamlMap, "type", PasswordType2.LETTERS_AND_DIGITS.toString());
PasswordType2 passwordType = PasswordType2.valueOf(passTypeString.toUpperCase());
- String lengthString = fetchValueDefault(passwordYamlMap, "length", "8");
+ String lengthString = ContestImportUtilities.fetchValueDefault(passwordYamlMap, "length", "8");
int length = getIntegerValue(lengthString, 8);
- String prefix = fetchValueDefault(passwordYamlMap, "prefix", "");
+ String prefix = ContestImportUtilities.fetchValueDefault(passwordYamlMap, "prefix", "");
/**
* Assign team passwords
@@ -837,9 +784,9 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S
/**
* Override output directory for files
*/
- targetDirectory = fetchValueDefault(passwordYamlMap, "outdirname", targetDirectory);
+ targetDirectory = ContestImportUtilities.fetchValueDefault(passwordYamlMap, "outdirname", targetDirectory);
- String passfilename = fetchValueDefault(passwordYamlMap, "passfile", targetDirectory + File.separator + MailMergeFile.PASSWORD_LIST_FILENNAME);
+ String passfilename = ContestImportUtilities.fetchValueDefault(passwordYamlMap, "passfile", targetDirectory + File.separator + MailMergeFile.PASSWORD_LIST_FILENNAME);
/**
* Write OS login passwords file (just a list of passwords in a text file)
@@ -847,7 +794,7 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S
generateOSPasswords(passfilename, updatedAccounts.length, passwordType, length, prefix);
- String mergefilename = fetchValueDefault(passwordYamlMap, "mergefile", targetDirectory + File.separator + MailMergeFile.DEFAULT_MERGE_OUTPUT_FILENAME);
+ String mergefilename = ContestImportUtilities.fetchValueDefault(passwordYamlMap, "mergefile", targetDirectory + File.separator + MailMergeFile.DEFAULT_MERGE_OUTPUT_FILENAME);
try {
/**
@@ -891,7 +838,7 @@ public IInternalContest fromYaml(IInternalContest contest, String[] yamlLines, S
contest.updateAccount(account);
// System.out.println("debug Added proxy account "+account.getClientId().toString());
} else {
- syntaxError("No such account for proxy of " + clientId.getClientType().toString() + " " + clientId.getClientNumber() + " at site " + clientId.getSiteNumber());
+ ContestImportUtilities.syntaxError("No such account for proxy of " + clientId.getClientType().toString() + " " + clientId.getClientNumber() + " at site " + clientId.getSiteNumber());
;
}
}
@@ -1069,7 +1016,7 @@ private void setScoreboardFreezeTime(IInternalContest contest, String scoreboard
if (scoreboardFreezeTime.length() > 2) {
long seconds = parseTimeIntoSeconds(scoreboardFreezeTime, -1);
if(seconds == -1) {
- syntaxError("Failed to parse scoreboard freeze time `" + scoreboardFreezeTime + "`, expected seconds or HH:MM:SS");
+ ContestImportUtilities.syntaxError("Failed to parse scoreboard freeze time `" + scoreboardFreezeTime + "`, expected seconds or HH:MM:SS");
}
scoreboardFreezeTime = ContestTime.formatTime(seconds);
}
@@ -1128,29 +1075,6 @@ private void setShortContestNameAndIdentifier(IInternalContest contest, String s
contest.setContestIdentifier(shortContestName);
}
- /**
- * Get boolean value for input key in map.
- *
- * Returns defaultVaue if no entry matches key.
- *
- * @param content
- * @param key
- * @param defaultValue
- * @return defaultValue or value from item in map.
- */
- private boolean fetchBooleanValue(Map content, String key, boolean defaultValue) {
- Object object = content.get(key);
- Boolean value = false;
- if (object == null) {
- return defaultValue;
- } else if (object instanceof Boolean) {
- value = (Boolean) content.get(key);
- } else if (object instanceof String) {
- value = getBooleanValue((String) content.get(key), defaultValue);
- }
- return value;
- }
-
private void addAutoJudgeSetting(IInternalContest contest, AutoJudgeSetting auto) {
Account account = contest.getAccount(auto.getClientId());
@@ -1176,21 +1100,21 @@ private Account[] getAccounts(String[] yamlLines) {
Vector accountVector = new Vector();
AccountList accountList = new AccountList();
- Map yamlContent = loadYaml(null, yamlLines);
- ArrayList