7195131: Update 2 compiler combo tests for repeating annotations to include package and default use cases
authordarcy
Thu, 07 Feb 2013 20:47:06 -0800
changeset 15709 95a659644d2e
parent 15708 fbf749010eba
child 15710 5792a085da41
7195131: Update 2 compiler combo tests for repeating annotations to include package and default use cases Reviewed-by: darcy Contributed-by: sonali.goel@oracle.com
langtools/test/tools/javac/annotations/repeatingAnnotations/combo/Helper.java
langtools/test/tools/javac/annotations/repeatingAnnotations/combo/TargetAnnoCombo.java
langtools/test/tools/javac/annotations/repeatingAnnotations/combo/TestCaseGenerator.java
--- a/langtools/test/tools/javac/annotations/repeatingAnnotations/combo/Helper.java	Wed Feb 06 23:10:35 2013 +0000
+++ b/langtools/test/tools/javac/annotations/repeatingAnnotations/combo/Helper.java	Thu Feb 07 20:47:06 2013 -0800
@@ -40,14 +40,17 @@
         IMPORTINHERITED("import java.lang.annotation.Inherited;\n"),
         IMPORTRETENTION("import java.lang.annotation.Retention;\n" +
                         "\nimport java.lang.annotation.RetentionPolicy;\n"),
+        IMPORTSTMTS("import java.lang.annotation.*;\n"),
         REPEATABLE("\n@Repeatable(FooContainer.class)\n"),
         CONTAINER("@interface FooContainer {\n" +"  Foo[] value();\n}\n"),
         BASE("@interface Foo {}\n"),
+        BASEANNO("@Foo"),
         REPEATABLEANNO("\n@Foo() @Foo()"),
         DEPRECATED("\n@Deprecated"),
         DOCUMENTED("\n@Documented"),
         INHERITED("\n@Inherited"),
-        RETENTION("@Retention(RetentionPolicy.#VAL)\n");
+        RETENTION("@Retention(RetentionPolicy.#VAL)\n"),
+        TARGET("\n@Target(#VAL)\n");
 
         private String val;
 
@@ -69,6 +72,7 @@
     public static final String template =
             "/*PACKAGE*/\n" +
             "//pkg test;\n\n" +
+            "/*ANNODATA*/\n" + // import statements, declaration of Foo/FooContainer
             "/*TYPE*/ //class\n" +
             "class #ClassName {\n" +
             "  /*FIELD*/ //instance var\n" +
@@ -97,7 +101,11 @@
             "interface TestInterface {}\n\n" +
             "/*TYPE*/\n" +
             "/*ANNOTATION_TYPE*/\n" +
-            "@interface TestAnnotationType{}\n";
+            "@interface TestAnnotationType{}\n" +
+            "class TestPkg {}\n" +
+            "class TestTypeAnno </*TYPE_PARAMETER*/ T extends Object> {\n" +
+            "  String /*TYPE_USE*/[] arr;\n" +
+            "}";
 
     // Create and compile FileObject using values for className and contents
     public static boolean compileCode(String className, String contents,
@@ -150,3 +158,4 @@
         }
     }
 }
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/annotations/repeatingAnnotations/combo/TargetAnnoCombo.java	Thu Feb 07 20:47:06 2013 -0800
@@ -0,0 +1,469 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/**
+ * @test
+ * @bug      7195131
+ * @author   sogoel
+ * @summary  Combo test for all possible combinations for Target values
+ * @build    Helper
+ * @compile  TargetAnnoCombo.java TestCaseGenerator.java
+ * @run main TargetAnnoCombo
+ */
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+import javax.tools.Diagnostic;
+import javax.tools.DiagnosticCollector;
+import javax.tools.JavaFileObject;
+
+/*
+ * TargetAnnoCombo gets a list of test case numbers using TestCaseGenerator.
+ * For each of the test case number, @Target sets for base and container annotations
+ * are determined, source files are generated, compiled, and the result is verified
+ * based on if the @Target set for base and container is a positive or negative combination.
+ *
+ * @Target sets for base and container annotations are determined using a bit mapping of
+ * 10 ElementType enum constants defined in JDK8.
+ *
+ * Bit      Target value
+ *  0  "ElementType.ANNOTATION_TYPE"
+ *  1  "ElementType.CONSTRUCTOR"
+ *  2  "ElementType.FIELD"
+ *  3  "ElementType.LOCAL_VARIABLE"
+ *  4  "ElementType.METHOD"
+ *  5  "ElementType.TYPE"
+ *  6  "ElementType.PARAMETER"
+ *  7  "ElementType.PACKAGE"
+ *  8  "ElementType.TYPE_USE"
+ *  9  "ElementType.TYPE_PARAMETER"
+ *
+ * Group 1:
+ * 20 bits mapping, representing a test case number, is used for all target set
+ * combinations ( 0 to 1048575 ) including empty @Target sets => @Target({}).
+ * From this 20 bits, 10 bits are for base followed by 10 bits for container
+ * where each bit maps to an ElementType enum constant defined in JDK8.
+ *
+ * Examples:
+ * Test case number: 4, binary: 100 => container=100, base=[], container=["ElementType.FIELD"]
+ * Test case number: 1003575, binary: 11110101000000110111 => base=1111010100, container=0000110111;
+ *                   base=["ElementType.PARAMETER", "ElementType.TYPE_USE", "ElementType.METHOD", "ElementType.FIELD", "ElementType.PACKAGE", "ElementType.TYPE_PARAMETER"],
+ *                   container=["ElementType.TYPE", "ElementType.METHOD", "ElementType.ANNOTATION_TYPE", "ElementType.CONSTRUCTOR", "ElementType.FIELD"]
+ *
+ * In the following groups, no @Target set is represented by null.
+ * Group 2:
+ * @Target is not defined on base.
+ * Target sets for container are determined using the 10-bit binary number
+ * resulting in 1024 test cases, mapping them to test case numbers from
+ * 1048576 to (1048576 + 1023) => 1048576 to 1049599.
+ *
+ * Example:
+ * Test case number: 1048587 => 1048587 - 1048576 = test case 11 in Group 2, binary: 1011 =>
+ *                   base = null,
+ *                   container = ["ElementType.ANNOTATION_TYPE","ElementType.CONSTRUCTOR","ElementType.LOCAL_VARIABLE"]
+ *
+ * Group 3:
+ * @Target is not defined on container
+ * Target sets for base are determined using the 10-bit binary number
+ * resulting in 1024 test cases, mapping them to test case numbers from
+ * 1049600 to (1049600 + 1023) => 1049600 to 1050623.
+ *
+ * Example:
+ * Test case number: 1049708 => 1049708 - 1049600 = test case 108 in Group 3, binary: 1101100 =>
+ *                   base = ["ElementType.FIELD", "ElementType.LOCAL_VARIABLE", "ElementType.TYPE", "ElementType.PARAMETER"],
+ *                   container = null
+ *
+ * For the above group, test case number: 1049855 gives compiler error, JDK-8006547 filed
+ *
+ * Group 4:
+ * @Target not defined for both base and container annotations.
+ *
+ * This is the last test and corresponds to test case number 1050624. base=null, container=null
+ *
+ * Examples to run this test:
+ * 1. Run a specific test case number:
+ *    ${JTREG} -DTestCaseNum=10782 -samevm -jdk:${JAVA_TEST} -reportDir ${REPORT} -workDir ${WORK} TargetAnnoCombo.java
+ * 2. Run specific number of tests:
+ *    ${JTREG} -DNumberOfTests=4 -samevm -jdk:${JAVA_TEST} -reportDir ${REPORT} -workDir ${WORK} TargetAnnoCombo.java
+ * 3. Run specific number of tests with a seed:
+ *    ${JTREG} -DNumberOfTests=4 -DTestSeed=-972894659 -samevm -jdk:${JAVA_TEST} -reportDir ${REPORT} -workDir ${WORK} TargetAnnoCombo.java
+ * 4. Run tests in default mode (number of tests = 1000):
+ *    ${JTREG} -DTestMode=DEFAULT -samevm -jdk:${JAVA_TEST} -reportDir ${REPORT} -workDir ${WORK} TargetAnnoCombo.java
+ * 5. Run all tests (FULL mode):
+ *    ${JTREG} -DTestMode=FULL -samevm -jdk:${JAVA_TEST} -reportDir ${REPORT} -workDir ${WORK} TargetAnnoCombo.java
+ *
+ */
+
+public class TargetAnnoCombo {
+    int errors = 0;
+    static final String TESTPKG = "testpkg";
+    /*
+     *  Set it to true to get more debug information including base and
+     *  container target sets for a given test case number
+     */
+    static final boolean DEBUG = false;
+
+    // JDK 5/6/7/8 Targets
+    static final String[] targetVals = {"ElementType.ANNOTATION_TYPE",
+      "ElementType.CONSTRUCTOR", "ElementType.FIELD",
+      "ElementType.LOCAL_VARIABLE", "ElementType.METHOD",
+      "ElementType.TYPE", "ElementType.PARAMETER",
+      "ElementType.PACKAGE", "ElementType.TYPE_USE",
+      "ElementType.TYPE_PARAMETER"};
+
+    // TYPE_USE and TYPE_PARAMETER (added in JDK8) are not part of default Target set
+    static final int DEFAULT_TARGET_CNT = 8;
+
+    public static void main(String args[]) throws Exception {
+
+        /* maxTestNum = (base and container combinations of targetVals elems [0 - 1048575 combos])
+         *              + (combinations where base or container has no Target [1024 combos])
+         *              + (no -1 even though 1st test is number 0 as last test is where both
+         *                 base and container have no target)
+         */
+
+        int maxTestNum = (int)Math.pow(2, 2*targetVals.length) + 2*(int)Math.pow(2, targetVals.length);
+        TestCaseGenerator tcg = new TestCaseGenerator(maxTestNum);
+        TargetAnnoCombo tac = new TargetAnnoCombo();
+
+        int testCtr = 0;
+        int testCase = -1;
+        while ( (testCase=tcg.getNextTestCase()) != -1 ) {
+            tac.executeTestCase(testCase, maxTestNum);
+            testCtr++;
+        }
+
+        System.out.println("Total tests run: " + testCtr);
+        if (tac.errors > 0)
+            throw new Exception(tac.errors + " errors found");
+    }
+
+    /*
+     * For given testCase, determine the base and container annotation Target sets,
+     * get if testCase should compile, get test source file(s), get compilation result and verify.
+     *
+     */
+    private void executeTestCase(int testCase, int maxTestNum) {
+
+        // Determine base and container annotation Target sets for the testCase
+        Set<String> baseAnnoTarget = null;
+        Set<String> conAnnoTarget = null;
+
+        //Number of base and container combinations [0 - 1048575 combos]
+        int baseContCombos = (int)Math.pow(2, 2*targetVals.length);
+        //Number of either base or container combinations when one of them has no @Target [1024 combos]
+        int targetValsCombos = (int)Math.pow(2, targetVals.length);
+
+        if (testCase >= baseContCombos) {
+            //Base annotation do not have @Target
+            if (testCase < baseContCombos + targetValsCombos) {
+                baseAnnoTarget = null;
+                conAnnoTarget = getSetFromBitVec(Integer.toBinaryString(testCase - baseContCombos));
+            } else if (testCase < baseContCombos + 2*targetValsCombos) {
+                //Container annotation do not have @Target
+                baseAnnoTarget = getSetFromBitVec(Integer.toBinaryString(testCase - baseContCombos - targetValsCombos));
+                conAnnoTarget = null;
+            } else {
+                //Both Base and Container annotation do not have @Target
+                baseAnnoTarget = null;
+                conAnnoTarget = null;
+            }
+        } else {
+            //TestCase number is represented as 10-bits for base followed by container bits
+            String bin = Integer.toBinaryString(testCase);
+            String base="", cont=bin;
+            if (bin.length() > targetVals.length){
+                base = bin.substring(0, bin.length() - targetVals.length);
+                cont = bin.substring(bin.length() - targetVals.length,bin.length());
+            }
+            baseAnnoTarget = getSetFromBitVec(base);
+            conAnnoTarget = getSetFromBitVec(cont);
+        }
+
+        debugPrint("Test case number = " + testCase + " => binary = " + Integer.toBinaryString(testCase));
+        debugPrint(" => baseAnnoTarget = " + baseAnnoTarget);
+        debugPrint(" => containerAnnoTarget = " + conAnnoTarget);
+
+        // Determine if a testCase should compile or not
+        String className = "TC" + testCase;
+        boolean shouldCompile = isValidSubSet(baseAnnoTarget, conAnnoTarget);
+
+        // Get test source file(s)
+        Iterable<? extends JavaFileObject> files = getFileList(className, baseAnnoTarget,
+                conAnnoTarget, shouldCompile);
+
+        // Get result of compiling test src file(s)
+        boolean result = getCompileResult(className, shouldCompile, files);
+
+        // List test src code if test fails
+        if(!result) {
+            System.out.println("FAIL: Test " + testCase);
+            try {
+                for (JavaFileObject f: files) {
+                    System.out.println("File: " + f.getName() + "\n" + f.getCharContent(true));
+                }
+            } catch (IOException ioe) {
+                System.out.println("Exception: " + ioe);
+            }
+        } else {
+            debugPrint("PASS: Test " + testCase);
+        }
+    }
+
+    // Get a Set<String> based on bits that are set to 1
+    public Set<String> getSetFromBitVec(String bitVec) {
+        Set<String> ret = new HashSet<>();
+        char[] bit = bitVec.toCharArray();
+        for (int i=bit.length-1, j=0; i>=0; i--, j++){
+            if (bit[i] == '1') {
+                ret.add(targetVals[j]);
+            }
+        }
+        return ret;
+    }
+
+    // Compile the test source file(s) and return test result
+    private boolean getCompileResult(String className, boolean shouldCompile,
+            Iterable<? extends JavaFileObject> files) {
+
+        DiagnosticCollector<JavaFileObject> diagnostics =
+                new DiagnosticCollector<JavaFileObject>();
+        Helper.compileCode(diagnostics, files);
+
+        // Test case pass or fail
+        boolean ok = false;
+
+        String errMesg = "";
+        int numDiags = diagnostics.getDiagnostics().size();
+
+        if (numDiags == 0) {
+            if (shouldCompile) {
+                debugPrint("Test passed, compiled as expected.");
+                ok = true;
+            } else {
+                errMesg = "Test failed, compiled unexpectedly.";
+                ok = false;
+            }
+        } else {
+            if (shouldCompile) {
+                // did not compile
+                errMesg = "Test failed, did not compile.";
+                ok = false;
+            } else {
+                // Error in compilation as expected
+                String expectedErrKey = "compiler.err.invalid.repeatable." +
+                        "annotation.incompatible.target";
+                for (Diagnostic<?> d : diagnostics.getDiagnostics()) {
+                    if((d.getKind() == Diagnostic.Kind.ERROR) &&
+                        d.getCode().contains(expectedErrKey)) {
+                        // Error message as expected
+                        debugPrint("Error message as expected.");
+                        ok = true;
+                        break;
+                    } else {
+                        // error message is incorrect
+                        ok = false;
+                    }
+                }
+                if (!ok) {
+                    errMesg = "Incorrect error received when compiling " +
+                        className + ", expected: " + expectedErrKey;
+                }
+            }
+        }
+
+        if(!ok) {
+            error(errMesg);
+            for (Diagnostic<?> d : diagnostics.getDiagnostics())
+                System.out.println(" Diags: " + d);
+        }
+        return ok;
+    }
+
+    private void debugPrint(String string) {
+        if(DEBUG)
+            System.out.println(string);
+    }
+
+    // Create src code and corresponding JavaFileObjects
+    private Iterable<? extends JavaFileObject> getFileList(String className,
+            Set<String> baseAnnoTarget, Set<String> conAnnoTarget,
+            boolean shouldCompile) {
+
+        String srcContent = "";
+        String pkgInfoContent = "";
+        String template = Helper.template;
+        String baseTarget = "", conTarget = "";
+
+        String target = Helper.ContentVars.TARGET.getVal();
+        if(baseAnnoTarget != null) {
+            baseTarget = target.replace("#VAL", baseAnnoTarget.toString())
+                                  .replace("[", "{").replace("]", "}");
+        }
+        if(conAnnoTarget != null) {
+            conTarget = target.replace("#VAL", conAnnoTarget.toString())
+                                 .replace("[", "{").replace("]", "}");
+        }
+
+        String annoData = Helper.ContentVars.IMPORTSTMTS.getVal() +
+                          conTarget +
+                          Helper.ContentVars.CONTAINER.getVal() +
+                          baseTarget +
+                          Helper.ContentVars.REPEATABLE.getVal() +
+                          Helper.ContentVars.BASE.getVal();
+
+        JavaFileObject pkgInfoFile = null;
+
+        /*
+         *  If shouldCompile = true and no @Target is specified for container annotation,
+         *  then all 8 ElementType enum constants are applicable as targets for
+         *  container annotation.
+         */
+        if(shouldCompile && conAnnoTarget == null) {
+            //conAnnoTarget = new HashSet<String>(Arrays.asList(targetVals));
+            conAnnoTarget = getDefaultTargetSet();
+        }
+
+        if(shouldCompile) {
+            boolean isPkgCasePresent = new ArrayList<String>(conAnnoTarget).contains("ElementType.PACKAGE");
+            String repeatableAnno = Helper.ContentVars.BASEANNO.getVal() + " " + Helper.ContentVars.BASEANNO.getVal();
+            for(String s: conAnnoTarget) {
+                s = s.replace("ElementType.","");
+                String replaceStr = "/*"+s+"*/";
+                if(s.equalsIgnoreCase("PACKAGE")) {
+                    //Create packageInfo file
+                    String pkgInfoName = TESTPKG + "." + "package-info";
+                    pkgInfoContent = repeatableAnno + "\npackage " + TESTPKG + ";" + annoData;
+                    pkgInfoFile = Helper.getFile(pkgInfoName, pkgInfoContent);
+                } else {
+                    template = template.replace(replaceStr, repeatableAnno);
+                    //srcContent = template.replace("#ClassName",className);
+                    if(!isPkgCasePresent) {
+                        srcContent = template.replace("/*ANNODATA*/", annoData).replace("#ClassName",className);
+                    } else {
+                        replaceStr = "/*PACKAGE*/";
+                        srcContent = template.replace(replaceStr, "package " + TESTPKG + ";")
+                                     .replace("#ClassName", className);
+                    }
+                }
+            }
+        } else {
+            // For invalid cases, compilation should fail at declaration site
+            template = "class #ClassName {}";
+            srcContent = annoData + template.replace("#ClassName",className);
+        }
+        JavaFileObject srcFile = Helper.getFile(className, srcContent);
+        Iterable<? extends JavaFileObject> files = null;
+        if(pkgInfoFile != null)
+            files = Arrays.asList(pkgInfoFile,srcFile);
+        else
+            files = Arrays.asList(srcFile);
+        return files;
+    }
+
+    private Set<String> getDefaultTargetSet() {
+        Set<String> defaultSet = new HashSet<>();
+        int ctr = 0;
+        for(String s : targetVals) {
+            if(ctr++ < DEFAULT_TARGET_CNT) {
+                defaultSet.add(s);
+            }
+        }
+        return defaultSet;
+    }
+
+    private boolean isValidSubSet(Set<String> baseAnnoTarget, Set<String> conAnnoTarget) {
+        /*
+         *  RULE 1: conAnnoTarget should be a subset of baseAnnoTarget
+         *  RULE 2: For empty @Target ({}) - annotation cannot be applied anywhere
+         *         - Empty sets for both is valid
+         *         - Empty baseTarget set is invalid with non-empty conTarget set
+         *         - Non-empty baseTarget set is valid with empty conTarget set
+         *  RULE 3: For no @Target specified - annotation can be applied to any JDK 7 targets
+         *         - No @Target for both is valid
+         *         - No @Target for baseTarget set with @Target conTarget set is valid
+         *         - @Target for baseTarget set with no @Target for conTarget is invalid
+         */
+
+
+        /* If baseAnno has no @Target, Foo can be either applied to @Target specified for container annotation
+         * else will be applicable for all default targets if no @Target is present for container annotation.
+         * In both cases, the set will be a valid set with no @Target for base annotation
+         */
+        if(baseAnnoTarget == null) {
+            if(conAnnoTarget == null) return true;
+            return !(conAnnoTarget.contains("ElementType.TYPE_USE") || conAnnoTarget.contains("ElementType.TYPE_PARAMETER"));
+        }
+
+        Set<String> tempBaseSet = new HashSet<>(baseAnnoTarget);
+        // If BaseAnno has TYPE, then ANNOTATION_TYPE is allowed by default
+        if(baseAnnoTarget.contains("ElementType.TYPE")) {
+            tempBaseSet.add("ElementType.ANNOTATION_TYPE");
+        }
+
+        /*
+         * If containerAnno has no @Target, only valid case if baseAnnoTarget has all targets defined
+         * else invalid set
+         */
+        if(conAnnoTarget == null) {
+            return (tempBaseSet.containsAll(getDefaultTargetSet()));
+        }
+
+        // At this point, neither conAnnoTarget or baseAnnoTarget are null
+        if(conAnnoTarget.size() == 0) return true;
+
+        // At this point, conAnnoTarget is non-empty
+        if (baseAnnoTarget.size() == 0) return false;
+
+        // At this point, neither conAnnoTarget or baseAnnoTarget are empty
+        return tempBaseSet.containsAll(conAnnoTarget);
+    }
+
+    void error(String msg) {
+        System.out.println("ERROR: " + msg);
+        errors++;
+    }
+
+    // Lists the start and end range for the given set of target vals
+    void showGroups() {
+        //Group 1: All target set combinations ( 0 to 1048575 ) including empty @Target sets => @Target({})
+        int grpEnd1 = (int)Math.pow(2, 2*targetVals.length) - 1;
+        System.out.println("[Group 1]: 0 - " + grpEnd1);
+
+        //Group 2: @Target not defined for base annotation ( 1048576 - 1049599 ).
+        System.out.print("[Group 2]: " + (grpEnd1 + 1) + " - ");
+        int grpEnd2 = grpEnd1 + 1 + (int)Math.pow(2, targetVals.length) - 1;
+        System.out.println(grpEnd2);
+
+        //Group 3: @Target not defined for container annotation ( 1049600 - 1050623 ).
+        System.out.print("[Group 3]: " + (grpEnd2 + 1) + " - ");
+        int grpEnd3 = grpEnd2 + 1 + (int)Math.pow(2, targetVals.length) - 1;
+        System.out.println(grpEnd3);
+
+        //Group 4: @Target not defined for both base and container annotations ( 1050624 ).
+        System.out.println("[Group 4]: " + (grpEnd3 + 1));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/annotations/repeatingAnnotations/combo/TestCaseGenerator.java	Thu Feb 07 20:47:06 2013 -0800
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Random;
+
+/* System properties:
+ * NumberOfTests, TestMode, and TestCaseNum are mutually exclusive
+ * TestSeed will be used only with NumberOfTests or TestMode, otherwise it will be ignored
+ * -DNumberOfTests=[0 to 2^20+2^11+1]
+ * -DTestMode=[FULL|DEFAULT]
+ * -DTestSeed=[seedNumber]
+ * -DTestCaseNum=[0 to 2^20+2^11+1]
+ */
+public class TestCaseGenerator {
+    // Total number of tests to be run
+    int numberOfTests = -1;
+    //Single test case
+    int testCaseNum = -1;
+    //Seed used to generate test cases
+    int testSeed;
+
+    int maxTestNum;
+    Random randNum;
+
+    // used in getNextTestCase
+    int curTestNum;
+    int testCompletedCount;
+    HashSet<Integer> uniqueTestSet;
+
+    static final int DEFAULT_TEST_COUNT = 250;
+
+    /*
+     *  Get parameter values from command line to set numberOfTests, testCaseNum,
+     *  and testSeed
+     */
+    public TestCaseGenerator(int maxTestNum) {
+        this.maxTestNum = maxTestNum;
+
+        // Set values for variables based on input from command line
+
+        // TestMode system property
+        String testModeVal = System.getProperty("TestMode");
+        if(testModeVal != null && !testModeVal.isEmpty()) {
+            switch (testModeVal.toUpperCase()) {
+            case "FULL":
+                numberOfTests = maxTestNum;
+                break;
+            case "DEFAULT":
+                numberOfTests = DEFAULT_TEST_COUNT;
+                break;
+            default:
+                System.out.println("Invalid property value " + testModeVal +
+                        " for numberOfTests. Possible range: 0 to " +
+                        maxTestNum + ". Ignoring property");
+                numberOfTests = -1;
+            }
+        }
+
+        // NumberOfTests system property
+        String numTestsStr = System.getProperty("NumberOfTests");
+        if(numTestsStr != null && !numTestsStr.isEmpty()) {
+            int numTests = -1;
+            try {
+                numTests = Integer.parseInt(numTestsStr);
+                if (numTests < 0 || numTests > maxTestNum) {
+                    throw new NumberFormatException();
+                }
+            } catch(NumberFormatException nfe) {
+                System.out.println("Invalid NumberOfTests property value " +
+                        numTestsStr + ". Possible range: 0 to " + maxTestNum +
+                        "Reset to default: " + DEFAULT_TEST_COUNT);
+                numTests = DEFAULT_TEST_COUNT;
+            }
+
+            if (numberOfTests != -1 && numTests != -1) {
+                System.out.println("TestMode and NumberOfTests cannot be set together. Ignoring TestMode.");
+            }
+            numberOfTests = numTests;
+        }
+
+        // TestSeed system property
+        String seedVal = System.getProperty("TestSeed");
+        if(seedVal != null && !seedVal.isEmpty()) {
+            try {
+                testSeed = Integer.parseInt(seedVal);
+            } catch(NumberFormatException nfe) {
+                Random srand = new Random();
+                testSeed = srand.nextInt();
+            }
+        } else {
+            Random srand = new Random();
+            testSeed = srand.nextInt();
+        }
+
+        // TestCaseNum system property
+        String testNumStr = System.getProperty("TestCaseNum");
+        if(testNumStr != null && !testNumStr.isEmpty()) {
+            try {
+                testCaseNum = Integer.parseInt(testNumStr);
+                if (testCaseNum < 0 || testCaseNum > maxTestNum) {
+                    throw new NumberFormatException();
+                }
+            } catch(NumberFormatException nfe) {
+                System.out.println("Invalid TestCaseNumber property value " +
+                        testNumStr + ". Possible value in range: 0 to " +
+                        maxTestNum + ". Defaulting to last test case.");
+                testCaseNum = maxTestNum;
+            }
+
+            if ( numberOfTests != -1) {
+                System.out.println("TestMode or NumberOfTests cannot be set along with TestCaseNum. Ignoring TestCaseNumber.");
+                testCaseNum = -1;
+            }
+        }
+
+        if (numberOfTests == -1 && testCaseNum == -1) {
+            numberOfTests = DEFAULT_TEST_COUNT;
+            System.out.println("Setting TestMode to default, will run " + numberOfTests + "tests.");
+        }
+
+        /*
+         *  By this point in code, we will have:
+         *  - testSeed: as per TestSeed or a Random one
+         *  - numberOfTests to run or -1 to denote not set
+         *  - testCaseNum to run or -1 to denote not set
+         */
+
+        /*
+         * If numberOfTests = maxTestNum, all tests are to be run,
+         * so no randNum will be required
+         */
+        if (numberOfTests != -1 && numberOfTests < maxTestNum) {
+            System.out.println("Seed = " + testSeed);
+            randNum = new Random(testSeed);
+            uniqueTestSet = new HashSet<>();
+        }
+
+        testCompletedCount = 0;
+        // to be used to keep sequential count when running all tests
+        curTestNum = 0;
+    }
+
+    /*
+     * returns next test case number to run
+     * returns -1 when there are no more tests to run
+     */
+    public int getNextTestCase() {
+        if (testCaseNum != -1) {
+            int nextTC = testCaseNum;
+            testCaseNum = -1;
+            return nextTC;
+        }
+        if (++testCompletedCount <= numberOfTests) {
+            if (numberOfTests == maxTestNum) {
+                //all the tests need to be run, so just return
+                //next test case sequentially
+                return curTestNum++;
+            } else {
+                int nextTC = -1;
+                // Ensuring unique test are run
+                while(!uniqueTestSet.add(nextTC = randNum.nextInt(maxTestNum))) {
+                }
+                return nextTC;
+            }
+        }
+        return -1;
+    }
+}