--- 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;
+ }
+}