6827009: Project Coin: Strings in Switch
authordarcy
Mon, 02 Nov 2009 21:36:59 -0800
changeset 4142 bd950c8f4fb3
parent 4141 4c47dc195a94
child 4143 7f5564de280a
6827009: Project Coin: Strings in Switch Reviewed-by: jjg, mcimadamore
langtools/src/share/classes/com/sun/tools/javac/code/Source.java
langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java
langtools/src/share/classes/com/sun/tools/javac/comp/Lower.java
langtools/src/share/classes/com/sun/tools/javac/resources/compiler.properties
langtools/test/tools/javac/StringsInSwitch/BadlyTypedLabel1.java
langtools/test/tools/javac/StringsInSwitch/BadlyTypedLabel1.out
langtools/test/tools/javac/StringsInSwitch/BadlyTypedLabel2.java
langtools/test/tools/javac/StringsInSwitch/BadlyTypedLabel2.out
langtools/test/tools/javac/StringsInSwitch/NonConstantLabel.java
langtools/test/tools/javac/StringsInSwitch/NonConstantLabel.out
langtools/test/tools/javac/StringsInSwitch/OneCaseSwitches.java
langtools/test/tools/javac/StringsInSwitch/RSCL1.out
langtools/test/tools/javac/StringsInSwitch/RSCL2.out
langtools/test/tools/javac/StringsInSwitch/RepeatedStringCaseLabels1.java
langtools/test/tools/javac/StringsInSwitch/RepeatedStringCaseLabels2.java
langtools/test/tools/javac/StringsInSwitch/StringSwitches.java
--- a/langtools/src/share/classes/com/sun/tools/javac/code/Source.java	Fri Oct 30 10:55:00 2009 -0700
+++ b/langtools/src/share/classes/com/sun/tools/javac/code/Source.java	Mon Nov 02 21:36:59 2009 -0800
@@ -110,6 +110,9 @@
     }
 
     /** Allow encoding errors, giving only warnings. */
+    public boolean allowStringsInSwitch() {
+        return compareTo(JDK1_7) >= 0;
+    }
     public boolean allowEncodingErrors() {
         return compareTo(JDK1_6) < 0;
     }
--- a/langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java	Fri Oct 30 10:55:00 2009 -0700
+++ b/langtools/src/share/classes/com/sun/tools/javac/comp/Attr.java	Mon Nov 02 21:36:59 2009 -0800
@@ -115,6 +115,8 @@
         allowBoxing = source.allowBoxing();
         allowCovariantReturns = source.allowCovariantReturns();
         allowAnonOuterThis = source.allowAnonOuterThis();
+        allowStringsInSwitch = source.allowStringsInSwitch();
+        sourceName = source.name;
         relax = (options.get("-retrofit") != null ||
                  options.get("-relax") != null);
         useBeforeDeclarationWarning = options.get("useBeforeDeclarationWarning") != null;
@@ -167,6 +169,16 @@
      */
     boolean enableSunApiLintControl;
 
+    /**
+     * Switch: allow strings in switch?
+     */
+    boolean allowStringsInSwitch;
+
+    /**
+     * Switch: name of source level; used for error reporting.
+     */
+    String sourceName;
+
     /** Check kind and type of given tree against protokind and prototype.
      *  If check succeeds, store type in tree and return it.
      *  If check fails, store errType in tree and return it.
@@ -886,7 +898,15 @@
         boolean enumSwitch =
             allowEnums &&
             (seltype.tsym.flags() & Flags.ENUM) != 0;
-        if (!enumSwitch)
+        boolean stringSwitch = false;
+        if (types.isSameType(seltype, syms.stringType)) {
+            if (allowStringsInSwitch) {
+                stringSwitch = true;
+            } else {
+                log.error(tree.selector.pos(), "string.switch.not.supported.in.source", sourceName);
+            }
+        }
+        if (!enumSwitch && !stringSwitch)
             seltype = chk.checkType(tree.selector.pos(), seltype, syms.intType);
 
         // Attribute all cases and
@@ -909,7 +929,8 @@
                     Type pattype = attribExpr(c.pat, switchEnv, seltype);
                     if (pattype.tag != ERROR) {
                         if (pattype.constValue() == null) {
-                            log.error(c.pat.pos(), "const.expr.req");
+                            log.error(c.pat.pos(),
+                                      (stringSwitch ? "string.const.req" : "const.expr.req"));
                         } else if (labels.contains(pattype.constValue())) {
                             log.error(c.pos(), "duplicate.case.label");
                         } else {
--- a/langtools/src/share/classes/com/sun/tools/javac/comp/Lower.java	Fri Oct 30 10:55:00 2009 -0700
+++ b/langtools/src/share/classes/com/sun/tools/javac/comp/Lower.java	Mon Nov 02 21:36:59 2009 -0800
@@ -357,7 +357,7 @@
      *          case 2: stmt2
      *          }
      *  </pre>
-     *  with the auxilliary table intialized as follows:
+     *  with the auxiliary table initialized as follows:
      *  <pre>
      *          class Outer$0 {
      *              synthetic final int[] $EnumMap$Color = new int[Color.values().length];
@@ -858,7 +858,7 @@
         int acode;                // The access code of the access method.
         List<Type> argtypes;      // The argument types of the access method.
         Type restype;             // The result type of the access method.
-        List<Type> thrown;        // The thrown execeptions of the access method.
+        List<Type> thrown;        // The thrown exceptions of the access method.
         switch (vsym.kind) {
         case VAR:
             acode = accessCode(tree, enclOp);
@@ -2463,7 +2463,7 @@
     // the dead code, which will not be eliminated during code generation.
     // Note that Flow.isFalse and Flow.isTrue only return true
     // for constant expressions in the sense of JLS 15.27, which
-    // are guaranteed to have no side-effects.  More agressive
+    // are guaranteed to have no side-effects.  More aggressive
     // constant propagation would require that we take care to
     // preserve possible side-effects in the condition expression.
 
@@ -2850,7 +2850,7 @@
 
         // If translated left hand side is an Apply, we are
         // seeing an access method invocation. In this case, return
-        // that access method invokation as result.
+        // that access method invocation as result.
         if (isUpdateOperator && tree.arg.getTag() == JCTree.APPLY) {
             result = tree.arg;
         } else {
@@ -2900,7 +2900,7 @@
     }
         // where
         /**
-         * A statment of the form
+         * A statement of the form
          *
          * <pre>
          *     for ( T v : arrayexpr ) stmt;
@@ -3109,12 +3109,17 @@
         Type selsuper = types.supertype(tree.selector.type);
         boolean enumSwitch = selsuper != null &&
             (tree.selector.type.tsym.flags() & ENUM) != 0;
-        Type target = enumSwitch ? tree.selector.type : syms.intType;
+        boolean stringSwitch = selsuper != null &&
+            types.isSameType(tree.selector.type, syms.stringType);
+        Type target = enumSwitch ? tree.selector.type :
+            (stringSwitch? syms.stringType : syms.intType);
         tree.selector = translate(tree.selector, target);
         tree.cases = translateCases(tree.cases);
         if (enumSwitch) {
             result = visitEnumSwitch(tree);
             patchTargets(result, tree, result);
+        } else if (stringSwitch) {
+            result = visitStringSwitch(tree);
         } else {
             result = tree;
         }
@@ -3144,6 +3149,184 @@
         return make.Switch(selector, cases.toList());
     }
 
+    public JCTree visitStringSwitch(JCSwitch tree) {
+        List<JCCase> caseList = tree.getCases();
+        int alternatives = caseList.size();
+
+        if (alternatives == 0) { // Strange but legal possibility
+            return make.at(tree.pos()).Exec(attr.makeNullCheck(tree.getExpression()));
+        } else {
+            /*
+             * The general approach used is to translate a single
+             * string switch statement into a series of two chained
+             * switch statements: the first a synthesized statement
+             * switching on the argument string's hash value and
+             * computing a string's position in the list of original
+             * case labels, if any, followed by a second switch on the
+             * computed integer value.  The second switch has the same
+             * code structure as the original string switch statement
+             * except that the string case labels are replaced with
+             * positional integer constants starting at 0.
+             *
+             * The first switch statement can be thought of as an
+             * inlined map from strings to their position in the case
+             * label list.  An alternate implementation would use an
+             * actual Map for this purpose, as done for enum switches.
+             *
+             * With some additional effort, it would be possible to
+             * use a single switch statement on the hash code of the
+             * argument, but care would need to be taken to preserve
+             * the proper control flow in the presence of hash
+             * collisions and other complications, such as
+             * fallthroughs.  Switch statements with one or two
+             * alternatives could also be specially translated into
+             * if-then statements to omit the computation of the hash
+             * code.
+             *
+             * The generated code assumes that the hashing algorithm
+             * of String is the same in the compilation environment as
+             * in the environment the code will run in.  The string
+             * hashing algorithm in the SE JDK has been unchanged
+             * since at least JDK 1.2.
+             */
+
+            ListBuffer<JCStatement> stmtList = new ListBuffer<JCStatement>();
+
+            // Map from String case labels to their original position in
+            // the list of case labels.
+            Map<String, Integer> caseLabelToPosition =
+                new LinkedHashMap<String, Integer>(alternatives + 1, 1.0f);
+
+            // Map of hash codes to the string case labels having that hashCode.
+            Map<Integer, Set<String>> hashToString =
+                new LinkedHashMap<Integer, Set<String>>(alternatives + 1, 1.0f);
+
+            int casePosition = 0;
+            for(JCCase oneCase : caseList) {
+                JCExpression expression = oneCase.getExpression();
+
+                if (expression != null) { // expression for a "default" case is null
+                    String labelExpr = (String) expression.type.constValue();
+                    Integer mapping = caseLabelToPosition.put(labelExpr, casePosition);
+                    assert mapping == null;
+                    int hashCode = labelExpr.hashCode();
+
+                    Set<String> stringSet = hashToString.get(hashCode);
+                    if (stringSet == null) {
+                        stringSet = new LinkedHashSet<String>(1, 1.0f);
+                        stringSet.add(labelExpr);
+                        hashToString.put(hashCode, stringSet);
+                    } else {
+                        boolean added = stringSet.add(labelExpr);
+                        assert added;
+                    }
+                }
+                casePosition++;
+            }
+
+            // Synthesize a switch statement that has the effect of
+            // mapping from a string to the integer position of that
+            // string in the list of case labels.  This is done by
+            // switching on the hashCode of the string followed by an
+            // if-then-else chain comparing the input for equality
+            // with all the case labels having that hash value.
+
+            /*
+             * s$ = top of stack;
+             * tmp$ = -1;
+             * switch($s.hashCode()) {
+             *     case caseLabel.hashCode:
+             *         if (s$.equals("caseLabel_1")
+             *           tmp$ = caseLabelToPosition("caseLabel_1");
+             *         else if (s$.equals("caseLabel_2"))
+             *           tmp$ = caseLabelToPosition("caseLabel_2");
+             *         ...
+             *         break;
+             * ...
+             * }
+             */
+
+            VarSymbol dollar_s = new VarSymbol(FINAL|SYNTHETIC,
+                                               names.fromString("s" + tree.pos + target.syntheticNameChar()),
+                                               syms.stringType,
+                                               currentMethodSym);
+            stmtList.append(make.at(tree.pos()).VarDef(dollar_s, tree.getExpression()).setType(dollar_s.type));
+
+            VarSymbol dollar_tmp = new VarSymbol(SYNTHETIC,
+                                                 names.fromString("tmp" + tree.pos + target.syntheticNameChar()),
+                                                 syms.intType,
+                                                 currentMethodSym);
+            JCVariableDecl dollar_tmp_def =
+                (JCVariableDecl)make.VarDef(dollar_tmp, make.Literal(INT, -1)).setType(dollar_tmp.type);
+            dollar_tmp_def.init.type = dollar_tmp.type = syms.intType;
+            stmtList.append(dollar_tmp_def);
+            ListBuffer<JCCase> caseBuffer = ListBuffer.lb();
+            // hashCode will trigger nullcheck on original switch expression
+            JCMethodInvocation hashCodeCall = makeCall(make.Ident(dollar_s),
+                                                       names.hashCode,
+                                                       List.<JCExpression>nil()).setType(syms.intType);
+            JCSwitch switch1 = make.Switch(hashCodeCall,
+                                        caseBuffer.toList());
+            for(Map.Entry<Integer, Set<String>> entry : hashToString.entrySet()) {
+                int hashCode = entry.getKey();
+                Set<String> stringsWithHashCode = entry.getValue();
+                assert stringsWithHashCode.size() >= 1;
+
+                JCStatement elsepart = null;
+                for(String caseLabel : stringsWithHashCode ) {
+                    JCMethodInvocation stringEqualsCall = makeCall(make.Ident(dollar_s),
+                                                                   names.equals,
+                                                                   List.<JCExpression>of(make.Literal(caseLabel)));
+                    elsepart = make.If(stringEqualsCall,
+                                       make.Exec(make.Assign(make.Ident(dollar_tmp),
+                                                             make.Literal(caseLabelToPosition.get(caseLabel))).
+                                                 setType(dollar_tmp.type)),
+                                       elsepart);
+                }
+
+                ListBuffer<JCStatement> lb = ListBuffer.lb();
+                JCBreak breakStmt = make.Break(null);
+                breakStmt.target = switch1;
+                lb.append(elsepart).append(breakStmt);
+
+                caseBuffer.append(make.Case(make.Literal(hashCode), lb.toList()));
+            }
+
+            switch1.cases = caseBuffer.toList();
+            stmtList.append(switch1);
+
+            // Make isomorphic switch tree replacing string labels
+            // with corresponding integer ones from the label to
+            // position map.
+
+            ListBuffer<JCCase> lb = ListBuffer.lb();
+            JCSwitch switch2 = make.Switch(make.Ident(dollar_tmp), lb.toList());
+            for(JCCase oneCase : caseList ) {
+                // Rewire up old unlabeled break statements to the
+                // replacement switch being created.
+                patchTargets(oneCase, tree, switch2);
+
+                boolean isDefault = (oneCase.getExpression() == null);
+                JCExpression caseExpr;
+                if (isDefault)
+                    caseExpr = null;
+                else {
+                    caseExpr = make.Literal(caseLabelToPosition.get((String)oneCase.
+                                                                    getExpression().
+                                                                    type.constValue()));
+                }
+
+                lb.append(make.Case(caseExpr,
+                                    oneCase.getStatements()));
+            }
+
+            switch2.cases = lb.toList();
+            stmtList.append(switch2);
+
+            return make.Block(0L, stmtList.toList());
+        }
+    }
+
     public void visitNewArray(JCNewArray tree) {
         tree.elemtype = translate(tree.elemtype);
         for (List<JCExpression> t = tree.dims; t.tail != null; t = t.tail)
--- a/langtools/src/share/classes/com/sun/tools/javac/resources/compiler.properties	Fri Oct 30 10:55:00 2009 -0700
+++ b/langtools/src/share/classes/com/sun/tools/javac/resources/compiler.properties	Mon Nov 02 21:36:59 2009 -0800
@@ -433,6 +433,8 @@
     Internal error: stack sim error on {0}
 compiler.err.static.imp.only.classes.and.interfaces=\
     static import only from classes and interfaces
+compiler.err.string.const.req=\
+    constant string expression required
 compiler.err.synthetic.name.conflict=\
     the symbol {0} conflicts with a compiler-synthesized symbol in {1}
 compiler.warn.synthetic.name.conflict=\
@@ -1226,6 +1228,10 @@
     diamond operator is not supported in -source {0}\n\
 (use -source 7 or higher to enable diamond operator)
 
+compiler.err.string.switch.not.supported.in.source=\
+    strings in switch are not supported in -source {0}\n\
+(use -source 7 or higher to enable strings in switch)
+
 ########################################
 # Diagnostics for where clause implementation
 # used by the RichDiagnosticFormatter.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/StringsInSwitch/BadlyTypedLabel1.java	Mon Nov 02 21:36:59 2009 -0800
@@ -0,0 +1,17 @@
+/*
+ * @test  /nodynamiccopyright/
+ * @bug 6827009
+ * @summary Check for case labels of different types.
+ * @compile/fail -source 6 BadlyTypedLabel1.java
+ * @compile/fail/ref=BadlyTypedLabel1.out -XDstdout -XDrawDiagnostics BadlyTypedLabel1.java
+ */
+class BadlyTypedLabel1 {
+    String m(String s) {
+        switch(s) {
+        case "Hello World":
+            return(s);
+        case 42:
+            return ("Don't forget your towel!");
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/StringsInSwitch/BadlyTypedLabel1.out	Mon Nov 02 21:36:59 2009 -0800
@@ -0,0 +1,2 @@
+BadlyTypedLabel1.java:13:14: compiler.err.prob.found.req: (compiler.misc.incompatible.types), int, java.lang.String
+1 error
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/StringsInSwitch/BadlyTypedLabel2.java	Mon Nov 02 21:36:59 2009 -0800
@@ -0,0 +1,19 @@
+/*
+ * @test  /nodynamiccopyright/
+ * @bug 6827009
+ * @summary Check for case lables of different types.
+ * @compile/fail -source 6 BadlyTypedLabel2.java
+ * @compile/fail/ref=BadlyTypedLabel2.out -XDstdout -XDrawDiagnostics BadlyTypedLabel2.java
+ */
+import static java.math.RoundingMode.*;
+
+class BadlyTypedLabel2 {
+    String m(String s) {
+        switch(s) {
+        case "Oh what a feeling...":
+            return(s);
+        case CEILING:
+            return ("... switching on the ceiling!");
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/StringsInSwitch/BadlyTypedLabel2.out	Mon Nov 02 21:36:59 2009 -0800
@@ -0,0 +1,2 @@
+BadlyTypedLabel2.java:15:14: compiler.err.prob.found.req: (compiler.misc.incompatible.types), java.math.RoundingMode, java.lang.String
+1 error
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/StringsInSwitch/NonConstantLabel.java	Mon Nov 02 21:36:59 2009 -0800
@@ -0,0 +1,18 @@
+/*
+ * @test  /nodynamiccopyright/
+ * @bug 6827009
+ * @summary Check for non-constant case labels.
+ * @compile/fail -source 6 NonConstantLabel.java
+ * @compile/fail/ref=NonConstantLabel.out -XDstdout -XDrawDiagnostics NonConstantLabel.java
+ */
+class NonConstantLabel {
+    String m(String s) {
+        String fauxConstant = "Goodbye Cruel World";
+        switch(s) {
+        case "Hello World":
+            return(s);
+        case fauxConstant:
+            return (s + s);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/StringsInSwitch/NonConstantLabel.out	Mon Nov 02 21:36:59 2009 -0800
@@ -0,0 +1,2 @@
+NonConstantLabel.java:14:14: compiler.err.string.const.req
+1 error
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/StringsInSwitch/OneCaseSwitches.java	Mon Nov 02 21:36:59 2009 -0800
@@ -0,0 +1,303 @@
+/*
+ * Copyright 2009 Sun Microsystems, Inc.  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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test
+ * @bug 6827009
+ * @summary Positive tests for strings in switch with few alternatives.
+ * @compile/fail -source 6 OneCaseSwitches.java
+ * @compile                OneCaseSwitches.java
+ * @run main OneCaseSwitches
+ * @author  Joseph D. Darcy
+ */
+
+import java.lang.reflect.*;
+import java.lang.annotation.*;
+import java.util.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+public class OneCaseSwitches {
+    @Retention(RUNTIME)
+    @interface TestMeForNull {}
+
+    @TestMeForNull
+    public static int zeroCasesNoDefault(String s, Set<String> stringSet, boolean expected) {
+        int failures = 0;
+        switch(s) {
+        }
+        return failures;
+    }
+
+    @TestMeForNull
+    public static int zeroCasesWithDefault(String s, Set<String> stringSet, boolean expected) {
+        int failures = 2;
+        boolean addResult;
+
+        switch(s) {
+        default:
+            failures = 0;
+            addResult = stringSet.add(s);
+            if (addResult != expected) {
+                failures++;
+                System.err.println("zeroCaseWithDefault: Expectedly got add result of " + addResult +
+                                   " on string " + s);
+            }
+        }
+
+        return failures;
+    }
+
+    @TestMeForNull
+    public static int zeroCasesWithDefaultBreak(String s, Set<String> stringSet, boolean expected) {
+        int failures = 2;
+        boolean addResult;
+
+        switch(s) {
+        default:
+            failures = zeroCasesWithDefault(s, stringSet, expected);
+            break;
+        }
+
+        return failures;
+    }
+
+    @TestMeForNull
+    public static int oneCaseNoDefault(String s, Set<String> stringSet, boolean expected) {
+        int failures = 2;
+        boolean addResult;
+
+        switch(s) {
+        case "foo":
+            failures = 0;
+            addResult = stringSet.add(s);
+            if (addResult != expected) {
+                failures++;
+                System.err.println("oneCaseNoDefault: Unexpectedly got add result of " + addResult +
+                                   " on string " + s);
+            }
+        }
+
+        return failures;
+    }
+
+    @TestMeForNull
+    public static int oneCaseNoDefaultBreak(String s, Set<String> stringSet, boolean expected) {
+        int failures = 2;
+        boolean addResult;
+
+        switch(s) {
+        case "foo":
+            failures = oneCaseNoDefaultBreak(s, stringSet, expected);
+            break;
+        }
+
+        return failures;
+    }
+
+    @TestMeForNull
+    public static int oneCaseWithDefault(String s, Set<String> stringSet, boolean expected) {
+        int failures = 2;
+        boolean addResult;;
+
+        switch(s) {
+        case "foo":
+            failures = 0;
+            addResult = stringSet.add(s);
+            if (addResult != expected) {
+                failures++;
+                System.err.println("oneCaseNoDefault: Expectedly got add result of " + addResult +
+                                   " on string " + s);
+            }
+            break;
+        default:
+            break;
+        }
+
+        return failures;
+    }
+
+    @TestMeForNull
+    public static int oneCaseBreakOnly(String s, Set<String> stringSet, boolean expected) {
+        int failures = 1;
+        switch(s) {
+        case "foo":
+            break;
+        }
+        failures = 0;
+        return failures;
+    }
+
+    @TestMeForNull
+    public static int oneCaseDefaultBreakOnly(String s, Set<String> stringSet, boolean expected) {
+        int failures = 1;
+        switch(s) {
+        default:
+            break;
+        }
+        failures = 0;
+        return failures;
+    }
+
+
+    static int testNullBehavior() {
+        int failures = 0;
+        int count = 0;
+
+        Method[] methods = OneCaseSwitches.class.getDeclaredMethods();
+
+        try {
+            for(Method method : methods) {
+                count++;
+                try {
+                    if (method.isAnnotationPresent(TestMeForNull.class)) {
+                        System.out.println("Testing method " + method);
+                        method.invoke(null, (String)null, emptyStringSet, false);
+                        failures++;
+                        System.err.println("Didn't get NPE as expected from " + method);
+                    }
+                } catch (InvocationTargetException ite) { // Expected
+                    Throwable targetException = ite.getTargetException();
+                    if (! (targetException instanceof NullPointerException)) {
+                        failures++; // Wrong exception thrown
+                        System.err.println("Didn't get expected target exception NPE, got " +
+                                           ite.getClass().getName());
+                    }
+                }
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+        if (count == 0) {
+            failures++;
+            System.err.println("Did not find any annotated methods.");
+        }
+        return failures;
+    }
+
+    static int testZeroCases() {
+        int failures = 0;
+        Set<String> noDefaultSet = new HashSet<String>();
+        Set<String> defaultSet   = new HashSet<String>();
+
+        zeroCasesNoDefault(FOO, noDefaultSet, false);
+        for(String word : words) {
+            zeroCasesNoDefault(word, noDefaultSet, false);
+        }
+
+        if (!noDefaultSet.isEmpty()) {
+            failures++;
+            System.err.println("Non-empty set after zeroCasesNoDefault");
+        }
+
+        for(String word : words) {
+            zeroCasesWithDefault(word, defaultSet, true);
+        }
+        if (defaultSet.size() != words.length) {
+            failures++;
+            System.err.println("Missing strings after zeroCasesWithDefault");
+        }
+
+        return failures;
+    }
+
+    static int testOneCaseNoDefault() {
+        int failures = 0;
+        Set<String> s = new HashSet<String>();
+        s.add("foo");
+        Set<String> fooSet = Collections.unmodifiableSet(s);
+        Set<String> testSet   = new HashSet<String>();
+
+        oneCaseNoDefault(FOO, testSet, true);
+        if (!testSet.equals(fooSet)) {
+            failures++;
+            System.err.println("Unexpected result from oneCaseNoDefault: didn't get {\"Foo\"}");
+        }
+
+        for(String word : words) {
+            oneCaseNoDefault(word, testSet, false);
+        }
+        if (!testSet.equals(fooSet)) {
+            failures++;
+            System.err.println("Unexpected result from oneCaseNoDefault: didn't get {\"Foo\"}");
+        }
+
+        return failures;
+    }
+
+    static int testBreakOnly() {
+        int failures = 0;
+
+        for(String word : words) {
+            failures += oneCaseBreakOnly(word, emptyStringSet, true);
+            failures += oneCaseDefaultBreakOnly(word, emptyStringSet, true);
+        }
+
+        return failures;
+    }
+
+    static int testExpressionEval() {
+        String s = "a";
+        int errors = 2;
+
+        System.out.println("Testing expression evaluation.");
+
+        switch (s + s) {
+        case "aa":
+            errors = 0;
+            break;
+
+        case "aaaa":
+            errors = 1;
+            System.err.println("Suspected bad expression evaluation.");
+            break;
+
+        default:
+             throw new RuntimeException("Should not reach here.");
+        }
+        return errors;
+    }
+
+    static final String FOO = "foo";
+
+    static final String[] words = {"baz",
+                                   "quux",
+                                   "wombat",
+                                   "\u0ccc\u0012"}; // hash collision with "foo"
+
+    final static Set<String> emptyStringSet = Collections.emptySet();
+
+    public static void main(String... args) {
+        int failures = 0;
+
+        failures += testNullBehavior();
+        failures += testZeroCases();
+        failures += testOneCaseNoDefault();
+        failures += testBreakOnly();
+        failures += testExpressionEval();
+
+        if (failures > 0) {
+            throw new RuntimeException();
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/StringsInSwitch/RSCL1.out	Mon Nov 02 21:36:59 2009 -0800
@@ -0,0 +1,2 @@
+RepeatedStringCaseLabels1.java:13:9: compiler.err.duplicate.case.label
+1 error
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/StringsInSwitch/RSCL2.out	Mon Nov 02 21:36:59 2009 -0800
@@ -0,0 +1,2 @@
+RepeatedStringCaseLabels2.java:14:9: compiler.err.duplicate.case.label
+1 error
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/StringsInSwitch/RepeatedStringCaseLabels1.java	Mon Nov 02 21:36:59 2009 -0800
@@ -0,0 +1,17 @@
+/*
+ * @test  /nodynamiccopyright/
+ * @bug 6827009
+ * @summary Check for repeated string case labels.
+ * @compile/fail -source 6 RepeatedStringCaseLabels1.java
+ * @compile/fail/ref=RSCL1.out -XDstdout -XDrawDiagnostics RepeatedStringCaseLabels1.java
+ */
+class RepeatedStringCaseLabels1 {
+    String m(String s) {
+        switch(s) {
+        case "Hello World":
+            return(s);
+        case "Hello" + " " + "World":
+            return (s + s);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/StringsInSwitch/RepeatedStringCaseLabels2.java	Mon Nov 02 21:36:59 2009 -0800
@@ -0,0 +1,18 @@
+/*
+ * @test  /nodynamiccopyright/
+ * @bug 6827009
+ * @summary Check for repeated string case labels.
+ * @compile/fail -source 6 RepeatedStringCaseLabels2.java
+ * @compile/fail/ref=RSCL2.out -XDstdout -XDrawDiagnostics RepeatedStringCaseLabels2.java
+ */
+class RepeatedStringCaseLabels2 {
+    String m(String s) {
+        final String constant = "Hello" + " " + "World";
+        switch(s) {
+        case "Hello World":
+            return(s);
+        case constant:
+            return (s + s);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/langtools/test/tools/javac/StringsInSwitch/StringSwitches.java	Mon Nov 02 21:36:59 2009 -0800
@@ -0,0 +1,263 @@
+/*
+ * Copyright 2009 Sun Microsystems, Inc.  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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+/*
+ * @test
+ * @bug 6827009
+ * @summary Positive tests for strings in switch.
+ * @author  Joseph D. Darcy
+ */
+
+public class StringSwitches {
+
+    public static void main(String... args) {
+        int failures = 0;
+
+        failures += testPileup();
+        failures += testSwitchingTwoWays();
+        failures += testNamedBreak();
+
+        if (failures > 0) {
+            throw new RuntimeException();
+        }
+    }
+
+    /*
+     * A zero length string and all strings consisting only of the
+     * zero character \u0000 have a hash code of zero.  This method
+     * maps such strings to the number of times \u0000 appears for 0
+     * through 6 occurrences.
+     */
+    private static int zeroHashes(String s) {
+        int result = Integer.MAX_VALUE;
+        switch(s) {
+        case "":
+            return 0;
+
+        case "\u0000":
+            result = 1; break;
+
+        case "\u0000\u0000":
+            return 2;
+
+        case "\u0000\u0000\u0000":
+            result = 3; break;
+
+        case "\u0000\u0000\u0000\u0000":
+            return 4;
+
+        case "\u0000\u0000\u0000\u0000\u0000":
+            result = 5; break;
+
+        case "\u0000\u0000\u0000\u0000\u0000\u0000":
+            return 6;
+
+        default:
+            result = -1;
+        }
+        return result;
+    }
+
+    private static int testPileup() {
+        int failures = 0;
+        String zero = "";
+        for(int i = 0; i <= 6; i++, zero += "\u0000") {
+            int result = zeroHashes(zero);
+            if (result != i) {
+                failures++;
+                System.err.printf("For string \"%s\" unexpectedly got %d instead of %d%n.",
+                                   zero, result, i);
+            }
+        }
+
+        if (zeroHashes("foo") != -1) {
+            failures++;
+            System.err.println("Failed to get -1 for input string.");
+        }
+
+        return failures;
+    }
+
+    /**
+     * Verify that a switch on an enum and a switch with the same
+     * structure on the string name of an enum compute equivalent
+     * values.
+     */
+    private static int testSwitchingTwoWays() {
+        int failures = 0;
+
+        for(MetaSynVar msv : MetaSynVar.values()) {
+            int enumResult = enumSwitch(msv);
+            int stringResult = stringSwitch(msv.name());
+
+            if (enumResult != stringResult) {
+                failures++;
+                System.err.printf("One value %s, computed 0x%x with the enum switch " +
+                                  "and 0x%x with the string one.%n",
+                                  msv, enumResult, stringResult);
+            }
+        }
+
+        return failures;
+    }
+
+    private static enum MetaSynVar {
+        FOO,
+        BAR,
+        BAZ,
+        QUX,
+        QUUX,
+        QUUUX,
+        MUMBLE,
+        FOOBAR;
+    }
+
+    private static int enumSwitch(MetaSynVar msv) {
+        int result = 0;
+        switch(msv) {
+        case FOO:
+            result |= (1<<0);
+            // fallthrough:
+
+        case BAR:
+        case BAZ:
+            result |= (1<<1);
+            break;
+
+        default:
+            switch(msv) {
+            case QUX:
+                result |= (1<<2);
+                break;
+
+            case QUUX:
+                result |= (1<<3);
+
+            default:
+                result |= (1<<4);
+            }
+            result |= (1<<5);
+            break;
+
+        case MUMBLE:
+            result |= (1<<6);
+            return result;
+
+        case FOOBAR:
+            result |= (1<<7);
+            break;
+        }
+        result |= (1<<8);
+        return result;
+    }
+
+    private static int stringSwitch(String msvName) {
+        int result = 0;
+        switch(msvName) {
+        case "FOO":
+            result |= (1<<0);
+            // fallthrough:
+
+        case "BAR":
+        case "BAZ":
+            result |= (1<<1);
+            break;
+
+        default:
+            switch(msvName) {
+            case "QUX":
+                result |= (1<<2);
+                break;
+
+            case "QUUX":
+                result |= (1<<3);
+
+            default:
+                result |= (1<<4);
+            }
+            result |= (1<<5);
+            break;
+
+        case "MUMBLE":
+            result |= (1<<6);
+            return result;
+
+        case "FOOBAR":
+            result |= (1<<7);
+            break;
+        }
+        result |= (1<<8);
+        return result;
+    }
+
+    private static int testNamedBreak() {
+        int failures = 0;
+        String[] testStrings  = {"a",       "b",  "c",       "d",      "e"};
+        int[]    testExpected = { 0b101011, 0b101, 0b100001, 0b101000, 0b10000};
+
+        for(int i = 0; i < testStrings.length; i++) {
+            int expected = testExpected[i];
+            int result = namedBreak(testStrings[i]);
+
+            if (result != expected) {
+                failures++;
+
+                System.err.printf("On input %s, got %d instead of %d.%n",
+                                  testStrings[i], result, expected);
+            }
+        }
+
+        return failures;
+    }
+
+    private static int namedBreak(String s) {
+        int result = 0;
+        outer: switch(s) {
+        case "a":
+        case "b":
+        case "c":
+            result |= (1<<0);
+        inner: switch(s + s) {
+            case "aa":
+                result |= (1<<1);
+                break inner;
+
+            case "cc":
+                break outer;
+
+            default:
+                result |= (1<<2);
+                return result;
+            }
+
+        case "d":
+            result |= (1<<3);
+            break outer;
+
+        default:
+            return result |= (1<<4);
+        }
+        result |= (1<<5);
+        return result;
+    }
+}