Mon, 02 Nov 2009 21:36:59 -0800
6827009: Project Coin: Strings in Switch
Reviewed-by: jjg, mcimadamore
1.1 --- a/src/share/classes/com/sun/tools/javac/code/Source.java Fri Oct 30 10:55:00 2009 -0700 1.2 +++ b/src/share/classes/com/sun/tools/javac/code/Source.java Mon Nov 02 21:36:59 2009 -0800 1.3 @@ -110,6 +110,9 @@ 1.4 } 1.5 1.6 /** Allow encoding errors, giving only warnings. */ 1.7 + public boolean allowStringsInSwitch() { 1.8 + return compareTo(JDK1_7) >= 0; 1.9 + } 1.10 public boolean allowEncodingErrors() { 1.11 return compareTo(JDK1_6) < 0; 1.12 }
2.1 --- a/src/share/classes/com/sun/tools/javac/comp/Attr.java Fri Oct 30 10:55:00 2009 -0700 2.2 +++ b/src/share/classes/com/sun/tools/javac/comp/Attr.java Mon Nov 02 21:36:59 2009 -0800 2.3 @@ -115,6 +115,8 @@ 2.4 allowBoxing = source.allowBoxing(); 2.5 allowCovariantReturns = source.allowCovariantReturns(); 2.6 allowAnonOuterThis = source.allowAnonOuterThis(); 2.7 + allowStringsInSwitch = source.allowStringsInSwitch(); 2.8 + sourceName = source.name; 2.9 relax = (options.get("-retrofit") != null || 2.10 options.get("-relax") != null); 2.11 useBeforeDeclarationWarning = options.get("useBeforeDeclarationWarning") != null; 2.12 @@ -167,6 +169,16 @@ 2.13 */ 2.14 boolean enableSunApiLintControl; 2.15 2.16 + /** 2.17 + * Switch: allow strings in switch? 2.18 + */ 2.19 + boolean allowStringsInSwitch; 2.20 + 2.21 + /** 2.22 + * Switch: name of source level; used for error reporting. 2.23 + */ 2.24 + String sourceName; 2.25 + 2.26 /** Check kind and type of given tree against protokind and prototype. 2.27 * If check succeeds, store type in tree and return it. 2.28 * If check fails, store errType in tree and return it. 2.29 @@ -886,7 +898,15 @@ 2.30 boolean enumSwitch = 2.31 allowEnums && 2.32 (seltype.tsym.flags() & Flags.ENUM) != 0; 2.33 - if (!enumSwitch) 2.34 + boolean stringSwitch = false; 2.35 + if (types.isSameType(seltype, syms.stringType)) { 2.36 + if (allowStringsInSwitch) { 2.37 + stringSwitch = true; 2.38 + } else { 2.39 + log.error(tree.selector.pos(), "string.switch.not.supported.in.source", sourceName); 2.40 + } 2.41 + } 2.42 + if (!enumSwitch && !stringSwitch) 2.43 seltype = chk.checkType(tree.selector.pos(), seltype, syms.intType); 2.44 2.45 // Attribute all cases and 2.46 @@ -909,7 +929,8 @@ 2.47 Type pattype = attribExpr(c.pat, switchEnv, seltype); 2.48 if (pattype.tag != ERROR) { 2.49 if (pattype.constValue() == null) { 2.50 - log.error(c.pat.pos(), "const.expr.req"); 2.51 + log.error(c.pat.pos(), 2.52 + (stringSwitch ? "string.const.req" : "const.expr.req")); 2.53 } else if (labels.contains(pattype.constValue())) { 2.54 log.error(c.pos(), "duplicate.case.label"); 2.55 } else {
3.1 --- a/src/share/classes/com/sun/tools/javac/comp/Lower.java Fri Oct 30 10:55:00 2009 -0700 3.2 +++ b/src/share/classes/com/sun/tools/javac/comp/Lower.java Mon Nov 02 21:36:59 2009 -0800 3.3 @@ -357,7 +357,7 @@ 3.4 * case 2: stmt2 3.5 * } 3.6 * </pre> 3.7 - * with the auxilliary table intialized as follows: 3.8 + * with the auxiliary table initialized as follows: 3.9 * <pre> 3.10 * class Outer$0 { 3.11 * synthetic final int[] $EnumMap$Color = new int[Color.values().length]; 3.12 @@ -858,7 +858,7 @@ 3.13 int acode; // The access code of the access method. 3.14 List<Type> argtypes; // The argument types of the access method. 3.15 Type restype; // The result type of the access method. 3.16 - List<Type> thrown; // The thrown execeptions of the access method. 3.17 + List<Type> thrown; // The thrown exceptions of the access method. 3.18 switch (vsym.kind) { 3.19 case VAR: 3.20 acode = accessCode(tree, enclOp); 3.21 @@ -2463,7 +2463,7 @@ 3.22 // the dead code, which will not be eliminated during code generation. 3.23 // Note that Flow.isFalse and Flow.isTrue only return true 3.24 // for constant expressions in the sense of JLS 15.27, which 3.25 - // are guaranteed to have no side-effects. More agressive 3.26 + // are guaranteed to have no side-effects. More aggressive 3.27 // constant propagation would require that we take care to 3.28 // preserve possible side-effects in the condition expression. 3.29 3.30 @@ -2850,7 +2850,7 @@ 3.31 3.32 // If translated left hand side is an Apply, we are 3.33 // seeing an access method invocation. In this case, return 3.34 - // that access method invokation as result. 3.35 + // that access method invocation as result. 3.36 if (isUpdateOperator && tree.arg.getTag() == JCTree.APPLY) { 3.37 result = tree.arg; 3.38 } else { 3.39 @@ -2900,7 +2900,7 @@ 3.40 } 3.41 // where 3.42 /** 3.43 - * A statment of the form 3.44 + * A statement of the form 3.45 * 3.46 * <pre> 3.47 * for ( T v : arrayexpr ) stmt; 3.48 @@ -3109,12 +3109,17 @@ 3.49 Type selsuper = types.supertype(tree.selector.type); 3.50 boolean enumSwitch = selsuper != null && 3.51 (tree.selector.type.tsym.flags() & ENUM) != 0; 3.52 - Type target = enumSwitch ? tree.selector.type : syms.intType; 3.53 + boolean stringSwitch = selsuper != null && 3.54 + types.isSameType(tree.selector.type, syms.stringType); 3.55 + Type target = enumSwitch ? tree.selector.type : 3.56 + (stringSwitch? syms.stringType : syms.intType); 3.57 tree.selector = translate(tree.selector, target); 3.58 tree.cases = translateCases(tree.cases); 3.59 if (enumSwitch) { 3.60 result = visitEnumSwitch(tree); 3.61 patchTargets(result, tree, result); 3.62 + } else if (stringSwitch) { 3.63 + result = visitStringSwitch(tree); 3.64 } else { 3.65 result = tree; 3.66 } 3.67 @@ -3144,6 +3149,184 @@ 3.68 return make.Switch(selector, cases.toList()); 3.69 } 3.70 3.71 + public JCTree visitStringSwitch(JCSwitch tree) { 3.72 + List<JCCase> caseList = tree.getCases(); 3.73 + int alternatives = caseList.size(); 3.74 + 3.75 + if (alternatives == 0) { // Strange but legal possibility 3.76 + return make.at(tree.pos()).Exec(attr.makeNullCheck(tree.getExpression())); 3.77 + } else { 3.78 + /* 3.79 + * The general approach used is to translate a single 3.80 + * string switch statement into a series of two chained 3.81 + * switch statements: the first a synthesized statement 3.82 + * switching on the argument string's hash value and 3.83 + * computing a string's position in the list of original 3.84 + * case labels, if any, followed by a second switch on the 3.85 + * computed integer value. The second switch has the same 3.86 + * code structure as the original string switch statement 3.87 + * except that the string case labels are replaced with 3.88 + * positional integer constants starting at 0. 3.89 + * 3.90 + * The first switch statement can be thought of as an 3.91 + * inlined map from strings to their position in the case 3.92 + * label list. An alternate implementation would use an 3.93 + * actual Map for this purpose, as done for enum switches. 3.94 + * 3.95 + * With some additional effort, it would be possible to 3.96 + * use a single switch statement on the hash code of the 3.97 + * argument, but care would need to be taken to preserve 3.98 + * the proper control flow in the presence of hash 3.99 + * collisions and other complications, such as 3.100 + * fallthroughs. Switch statements with one or two 3.101 + * alternatives could also be specially translated into 3.102 + * if-then statements to omit the computation of the hash 3.103 + * code. 3.104 + * 3.105 + * The generated code assumes that the hashing algorithm 3.106 + * of String is the same in the compilation environment as 3.107 + * in the environment the code will run in. The string 3.108 + * hashing algorithm in the SE JDK has been unchanged 3.109 + * since at least JDK 1.2. 3.110 + */ 3.111 + 3.112 + ListBuffer<JCStatement> stmtList = new ListBuffer<JCStatement>(); 3.113 + 3.114 + // Map from String case labels to their original position in 3.115 + // the list of case labels. 3.116 + Map<String, Integer> caseLabelToPosition = 3.117 + new LinkedHashMap<String, Integer>(alternatives + 1, 1.0f); 3.118 + 3.119 + // Map of hash codes to the string case labels having that hashCode. 3.120 + Map<Integer, Set<String>> hashToString = 3.121 + new LinkedHashMap<Integer, Set<String>>(alternatives + 1, 1.0f); 3.122 + 3.123 + int casePosition = 0; 3.124 + for(JCCase oneCase : caseList) { 3.125 + JCExpression expression = oneCase.getExpression(); 3.126 + 3.127 + if (expression != null) { // expression for a "default" case is null 3.128 + String labelExpr = (String) expression.type.constValue(); 3.129 + Integer mapping = caseLabelToPosition.put(labelExpr, casePosition); 3.130 + assert mapping == null; 3.131 + int hashCode = labelExpr.hashCode(); 3.132 + 3.133 + Set<String> stringSet = hashToString.get(hashCode); 3.134 + if (stringSet == null) { 3.135 + stringSet = new LinkedHashSet<String>(1, 1.0f); 3.136 + stringSet.add(labelExpr); 3.137 + hashToString.put(hashCode, stringSet); 3.138 + } else { 3.139 + boolean added = stringSet.add(labelExpr); 3.140 + assert added; 3.141 + } 3.142 + } 3.143 + casePosition++; 3.144 + } 3.145 + 3.146 + // Synthesize a switch statement that has the effect of 3.147 + // mapping from a string to the integer position of that 3.148 + // string in the list of case labels. This is done by 3.149 + // switching on the hashCode of the string followed by an 3.150 + // if-then-else chain comparing the input for equality 3.151 + // with all the case labels having that hash value. 3.152 + 3.153 + /* 3.154 + * s$ = top of stack; 3.155 + * tmp$ = -1; 3.156 + * switch($s.hashCode()) { 3.157 + * case caseLabel.hashCode: 3.158 + * if (s$.equals("caseLabel_1") 3.159 + * tmp$ = caseLabelToPosition("caseLabel_1"); 3.160 + * else if (s$.equals("caseLabel_2")) 3.161 + * tmp$ = caseLabelToPosition("caseLabel_2"); 3.162 + * ... 3.163 + * break; 3.164 + * ... 3.165 + * } 3.166 + */ 3.167 + 3.168 + VarSymbol dollar_s = new VarSymbol(FINAL|SYNTHETIC, 3.169 + names.fromString("s" + tree.pos + target.syntheticNameChar()), 3.170 + syms.stringType, 3.171 + currentMethodSym); 3.172 + stmtList.append(make.at(tree.pos()).VarDef(dollar_s, tree.getExpression()).setType(dollar_s.type)); 3.173 + 3.174 + VarSymbol dollar_tmp = new VarSymbol(SYNTHETIC, 3.175 + names.fromString("tmp" + tree.pos + target.syntheticNameChar()), 3.176 + syms.intType, 3.177 + currentMethodSym); 3.178 + JCVariableDecl dollar_tmp_def = 3.179 + (JCVariableDecl)make.VarDef(dollar_tmp, make.Literal(INT, -1)).setType(dollar_tmp.type); 3.180 + dollar_tmp_def.init.type = dollar_tmp.type = syms.intType; 3.181 + stmtList.append(dollar_tmp_def); 3.182 + ListBuffer<JCCase> caseBuffer = ListBuffer.lb(); 3.183 + // hashCode will trigger nullcheck on original switch expression 3.184 + JCMethodInvocation hashCodeCall = makeCall(make.Ident(dollar_s), 3.185 + names.hashCode, 3.186 + List.<JCExpression>nil()).setType(syms.intType); 3.187 + JCSwitch switch1 = make.Switch(hashCodeCall, 3.188 + caseBuffer.toList()); 3.189 + for(Map.Entry<Integer, Set<String>> entry : hashToString.entrySet()) { 3.190 + int hashCode = entry.getKey(); 3.191 + Set<String> stringsWithHashCode = entry.getValue(); 3.192 + assert stringsWithHashCode.size() >= 1; 3.193 + 3.194 + JCStatement elsepart = null; 3.195 + for(String caseLabel : stringsWithHashCode ) { 3.196 + JCMethodInvocation stringEqualsCall = makeCall(make.Ident(dollar_s), 3.197 + names.equals, 3.198 + List.<JCExpression>of(make.Literal(caseLabel))); 3.199 + elsepart = make.If(stringEqualsCall, 3.200 + make.Exec(make.Assign(make.Ident(dollar_tmp), 3.201 + make.Literal(caseLabelToPosition.get(caseLabel))). 3.202 + setType(dollar_tmp.type)), 3.203 + elsepart); 3.204 + } 3.205 + 3.206 + ListBuffer<JCStatement> lb = ListBuffer.lb(); 3.207 + JCBreak breakStmt = make.Break(null); 3.208 + breakStmt.target = switch1; 3.209 + lb.append(elsepart).append(breakStmt); 3.210 + 3.211 + caseBuffer.append(make.Case(make.Literal(hashCode), lb.toList())); 3.212 + } 3.213 + 3.214 + switch1.cases = caseBuffer.toList(); 3.215 + stmtList.append(switch1); 3.216 + 3.217 + // Make isomorphic switch tree replacing string labels 3.218 + // with corresponding integer ones from the label to 3.219 + // position map. 3.220 + 3.221 + ListBuffer<JCCase> lb = ListBuffer.lb(); 3.222 + JCSwitch switch2 = make.Switch(make.Ident(dollar_tmp), lb.toList()); 3.223 + for(JCCase oneCase : caseList ) { 3.224 + // Rewire up old unlabeled break statements to the 3.225 + // replacement switch being created. 3.226 + patchTargets(oneCase, tree, switch2); 3.227 + 3.228 + boolean isDefault = (oneCase.getExpression() == null); 3.229 + JCExpression caseExpr; 3.230 + if (isDefault) 3.231 + caseExpr = null; 3.232 + else { 3.233 + caseExpr = make.Literal(caseLabelToPosition.get((String)oneCase. 3.234 + getExpression(). 3.235 + type.constValue())); 3.236 + } 3.237 + 3.238 + lb.append(make.Case(caseExpr, 3.239 + oneCase.getStatements())); 3.240 + } 3.241 + 3.242 + switch2.cases = lb.toList(); 3.243 + stmtList.append(switch2); 3.244 + 3.245 + return make.Block(0L, stmtList.toList()); 3.246 + } 3.247 + } 3.248 + 3.249 public void visitNewArray(JCNewArray tree) { 3.250 tree.elemtype = translate(tree.elemtype); 3.251 for (List<JCExpression> t = tree.dims; t.tail != null; t = t.tail)
4.1 --- a/src/share/classes/com/sun/tools/javac/resources/compiler.properties Fri Oct 30 10:55:00 2009 -0700 4.2 +++ b/src/share/classes/com/sun/tools/javac/resources/compiler.properties Mon Nov 02 21:36:59 2009 -0800 4.3 @@ -433,6 +433,8 @@ 4.4 Internal error: stack sim error on {0} 4.5 compiler.err.static.imp.only.classes.and.interfaces=\ 4.6 static import only from classes and interfaces 4.7 +compiler.err.string.const.req=\ 4.8 + constant string expression required 4.9 compiler.err.synthetic.name.conflict=\ 4.10 the symbol {0} conflicts with a compiler-synthesized symbol in {1} 4.11 compiler.warn.synthetic.name.conflict=\ 4.12 @@ -1226,6 +1228,10 @@ 4.13 diamond operator is not supported in -source {0}\n\ 4.14 (use -source 7 or higher to enable diamond operator) 4.15 4.16 +compiler.err.string.switch.not.supported.in.source=\ 4.17 + strings in switch are not supported in -source {0}\n\ 4.18 +(use -source 7 or higher to enable strings in switch) 4.19 + 4.20 ######################################## 4.21 # Diagnostics for where clause implementation 4.22 # used by the RichDiagnosticFormatter.
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 5.2 +++ b/test/tools/javac/StringsInSwitch/BadlyTypedLabel1.java Mon Nov 02 21:36:59 2009 -0800 5.3 @@ -0,0 +1,17 @@ 5.4 +/* 5.5 + * @test /nodynamiccopyright/ 5.6 + * @bug 6827009 5.7 + * @summary Check for case labels of different types. 5.8 + * @compile/fail -source 6 BadlyTypedLabel1.java 5.9 + * @compile/fail/ref=BadlyTypedLabel1.out -XDstdout -XDrawDiagnostics BadlyTypedLabel1.java 5.10 + */ 5.11 +class BadlyTypedLabel1 { 5.12 + String m(String s) { 5.13 + switch(s) { 5.14 + case "Hello World": 5.15 + return(s); 5.16 + case 42: 5.17 + return ("Don't forget your towel!"); 5.18 + } 5.19 + } 5.20 +}
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 6.2 +++ b/test/tools/javac/StringsInSwitch/BadlyTypedLabel1.out Mon Nov 02 21:36:59 2009 -0800 6.3 @@ -0,0 +1,2 @@ 6.4 +BadlyTypedLabel1.java:13:14: compiler.err.prob.found.req: (compiler.misc.incompatible.types), int, java.lang.String 6.5 +1 error
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 7.2 +++ b/test/tools/javac/StringsInSwitch/BadlyTypedLabel2.java Mon Nov 02 21:36:59 2009 -0800 7.3 @@ -0,0 +1,19 @@ 7.4 +/* 7.5 + * @test /nodynamiccopyright/ 7.6 + * @bug 6827009 7.7 + * @summary Check for case lables of different types. 7.8 + * @compile/fail -source 6 BadlyTypedLabel2.java 7.9 + * @compile/fail/ref=BadlyTypedLabel2.out -XDstdout -XDrawDiagnostics BadlyTypedLabel2.java 7.10 + */ 7.11 +import static java.math.RoundingMode.*; 7.12 + 7.13 +class BadlyTypedLabel2 { 7.14 + String m(String s) { 7.15 + switch(s) { 7.16 + case "Oh what a feeling...": 7.17 + return(s); 7.18 + case CEILING: 7.19 + return ("... switching on the ceiling!"); 7.20 + } 7.21 + } 7.22 +}
8.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 8.2 +++ b/test/tools/javac/StringsInSwitch/BadlyTypedLabel2.out Mon Nov 02 21:36:59 2009 -0800 8.3 @@ -0,0 +1,2 @@ 8.4 +BadlyTypedLabel2.java:15:14: compiler.err.prob.found.req: (compiler.misc.incompatible.types), java.math.RoundingMode, java.lang.String 8.5 +1 error
9.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 9.2 +++ b/test/tools/javac/StringsInSwitch/NonConstantLabel.java Mon Nov 02 21:36:59 2009 -0800 9.3 @@ -0,0 +1,18 @@ 9.4 +/* 9.5 + * @test /nodynamiccopyright/ 9.6 + * @bug 6827009 9.7 + * @summary Check for non-constant case labels. 9.8 + * @compile/fail -source 6 NonConstantLabel.java 9.9 + * @compile/fail/ref=NonConstantLabel.out -XDstdout -XDrawDiagnostics NonConstantLabel.java 9.10 + */ 9.11 +class NonConstantLabel { 9.12 + String m(String s) { 9.13 + String fauxConstant = "Goodbye Cruel World"; 9.14 + switch(s) { 9.15 + case "Hello World": 9.16 + return(s); 9.17 + case fauxConstant: 9.18 + return (s + s); 9.19 + } 9.20 + } 9.21 +}
10.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 10.2 +++ b/test/tools/javac/StringsInSwitch/NonConstantLabel.out Mon Nov 02 21:36:59 2009 -0800 10.3 @@ -0,0 +1,2 @@ 10.4 +NonConstantLabel.java:14:14: compiler.err.string.const.req 10.5 +1 error
11.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 11.2 +++ b/test/tools/javac/StringsInSwitch/OneCaseSwitches.java Mon Nov 02 21:36:59 2009 -0800 11.3 @@ -0,0 +1,303 @@ 11.4 +/* 11.5 + * Copyright 2009 Sun Microsystems, Inc. All Rights Reserved. 11.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 11.7 + * 11.8 + * This code is free software; you can redistribute it and/or modify it 11.9 + * under the terms of the GNU General Public License version 2 only, as 11.10 + * published by the Free Software Foundation. 11.11 + * 11.12 + * This code is distributed in the hope that it will be useful, but WITHOUT 11.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11.14 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 11.15 + * version 2 for more details (a copy is included in the LICENSE file that 11.16 + * accompanied this code). 11.17 + * 11.18 + * You should have received a copy of the GNU General Public License version 11.19 + * 2 along with this work; if not, write to the Free Software Foundation, 11.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 11.21 + * 11.22 + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, 11.23 + * CA 95054 USA or visit www.sun.com if you need additional information or 11.24 + * have any questions. 11.25 + */ 11.26 + 11.27 +/* 11.28 + * @test 11.29 + * @bug 6827009 11.30 + * @summary Positive tests for strings in switch with few alternatives. 11.31 + * @compile/fail -source 6 OneCaseSwitches.java 11.32 + * @compile OneCaseSwitches.java 11.33 + * @run main OneCaseSwitches 11.34 + * @author Joseph D. Darcy 11.35 + */ 11.36 + 11.37 +import java.lang.reflect.*; 11.38 +import java.lang.annotation.*; 11.39 +import java.util.*; 11.40 +import static java.lang.annotation.RetentionPolicy.*; 11.41 + 11.42 +public class OneCaseSwitches { 11.43 + @Retention(RUNTIME) 11.44 + @interface TestMeForNull {} 11.45 + 11.46 + @TestMeForNull 11.47 + public static int zeroCasesNoDefault(String s, Set<String> stringSet, boolean expected) { 11.48 + int failures = 0; 11.49 + switch(s) { 11.50 + } 11.51 + return failures; 11.52 + } 11.53 + 11.54 + @TestMeForNull 11.55 + public static int zeroCasesWithDefault(String s, Set<String> stringSet, boolean expected) { 11.56 + int failures = 2; 11.57 + boolean addResult; 11.58 + 11.59 + switch(s) { 11.60 + default: 11.61 + failures = 0; 11.62 + addResult = stringSet.add(s); 11.63 + if (addResult != expected) { 11.64 + failures++; 11.65 + System.err.println("zeroCaseWithDefault: Expectedly got add result of " + addResult + 11.66 + " on string " + s); 11.67 + } 11.68 + } 11.69 + 11.70 + return failures; 11.71 + } 11.72 + 11.73 + @TestMeForNull 11.74 + public static int zeroCasesWithDefaultBreak(String s, Set<String> stringSet, boolean expected) { 11.75 + int failures = 2; 11.76 + boolean addResult; 11.77 + 11.78 + switch(s) { 11.79 + default: 11.80 + failures = zeroCasesWithDefault(s, stringSet, expected); 11.81 + break; 11.82 + } 11.83 + 11.84 + return failures; 11.85 + } 11.86 + 11.87 + @TestMeForNull 11.88 + public static int oneCaseNoDefault(String s, Set<String> stringSet, boolean expected) { 11.89 + int failures = 2; 11.90 + boolean addResult; 11.91 + 11.92 + switch(s) { 11.93 + case "foo": 11.94 + failures = 0; 11.95 + addResult = stringSet.add(s); 11.96 + if (addResult != expected) { 11.97 + failures++; 11.98 + System.err.println("oneCaseNoDefault: Unexpectedly got add result of " + addResult + 11.99 + " on string " + s); 11.100 + } 11.101 + } 11.102 + 11.103 + return failures; 11.104 + } 11.105 + 11.106 + @TestMeForNull 11.107 + public static int oneCaseNoDefaultBreak(String s, Set<String> stringSet, boolean expected) { 11.108 + int failures = 2; 11.109 + boolean addResult; 11.110 + 11.111 + switch(s) { 11.112 + case "foo": 11.113 + failures = oneCaseNoDefaultBreak(s, stringSet, expected); 11.114 + break; 11.115 + } 11.116 + 11.117 + return failures; 11.118 + } 11.119 + 11.120 + @TestMeForNull 11.121 + public static int oneCaseWithDefault(String s, Set<String> stringSet, boolean expected) { 11.122 + int failures = 2; 11.123 + boolean addResult;; 11.124 + 11.125 + switch(s) { 11.126 + case "foo": 11.127 + failures = 0; 11.128 + addResult = stringSet.add(s); 11.129 + if (addResult != expected) { 11.130 + failures++; 11.131 + System.err.println("oneCaseNoDefault: Expectedly got add result of " + addResult + 11.132 + " on string " + s); 11.133 + } 11.134 + break; 11.135 + default: 11.136 + break; 11.137 + } 11.138 + 11.139 + return failures; 11.140 + } 11.141 + 11.142 + @TestMeForNull 11.143 + public static int oneCaseBreakOnly(String s, Set<String> stringSet, boolean expected) { 11.144 + int failures = 1; 11.145 + switch(s) { 11.146 + case "foo": 11.147 + break; 11.148 + } 11.149 + failures = 0; 11.150 + return failures; 11.151 + } 11.152 + 11.153 + @TestMeForNull 11.154 + public static int oneCaseDefaultBreakOnly(String s, Set<String> stringSet, boolean expected) { 11.155 + int failures = 1; 11.156 + switch(s) { 11.157 + default: 11.158 + break; 11.159 + } 11.160 + failures = 0; 11.161 + return failures; 11.162 + } 11.163 + 11.164 + 11.165 + static int testNullBehavior() { 11.166 + int failures = 0; 11.167 + int count = 0; 11.168 + 11.169 + Method[] methods = OneCaseSwitches.class.getDeclaredMethods(); 11.170 + 11.171 + try { 11.172 + for(Method method : methods) { 11.173 + count++; 11.174 + try { 11.175 + if (method.isAnnotationPresent(TestMeForNull.class)) { 11.176 + System.out.println("Testing method " + method); 11.177 + method.invoke(null, (String)null, emptyStringSet, false); 11.178 + failures++; 11.179 + System.err.println("Didn't get NPE as expected from " + method); 11.180 + } 11.181 + } catch (InvocationTargetException ite) { // Expected 11.182 + Throwable targetException = ite.getTargetException(); 11.183 + if (! (targetException instanceof NullPointerException)) { 11.184 + failures++; // Wrong exception thrown 11.185 + System.err.println("Didn't get expected target exception NPE, got " + 11.186 + ite.getClass().getName()); 11.187 + } 11.188 + } 11.189 + } 11.190 + } catch (Exception e) { 11.191 + throw new RuntimeException(e); 11.192 + } 11.193 + 11.194 + if (count == 0) { 11.195 + failures++; 11.196 + System.err.println("Did not find any annotated methods."); 11.197 + } 11.198 + return failures; 11.199 + } 11.200 + 11.201 + static int testZeroCases() { 11.202 + int failures = 0; 11.203 + Set<String> noDefaultSet = new HashSet<String>(); 11.204 + Set<String> defaultSet = new HashSet<String>(); 11.205 + 11.206 + zeroCasesNoDefault(FOO, noDefaultSet, false); 11.207 + for(String word : words) { 11.208 + zeroCasesNoDefault(word, noDefaultSet, false); 11.209 + } 11.210 + 11.211 + if (!noDefaultSet.isEmpty()) { 11.212 + failures++; 11.213 + System.err.println("Non-empty set after zeroCasesNoDefault"); 11.214 + } 11.215 + 11.216 + for(String word : words) { 11.217 + zeroCasesWithDefault(word, defaultSet, true); 11.218 + } 11.219 + if (defaultSet.size() != words.length) { 11.220 + failures++; 11.221 + System.err.println("Missing strings after zeroCasesWithDefault"); 11.222 + } 11.223 + 11.224 + return failures; 11.225 + } 11.226 + 11.227 + static int testOneCaseNoDefault() { 11.228 + int failures = 0; 11.229 + Set<String> s = new HashSet<String>(); 11.230 + s.add("foo"); 11.231 + Set<String> fooSet = Collections.unmodifiableSet(s); 11.232 + Set<String> testSet = new HashSet<String>(); 11.233 + 11.234 + oneCaseNoDefault(FOO, testSet, true); 11.235 + if (!testSet.equals(fooSet)) { 11.236 + failures++; 11.237 + System.err.println("Unexpected result from oneCaseNoDefault: didn't get {\"Foo\"}"); 11.238 + } 11.239 + 11.240 + for(String word : words) { 11.241 + oneCaseNoDefault(word, testSet, false); 11.242 + } 11.243 + if (!testSet.equals(fooSet)) { 11.244 + failures++; 11.245 + System.err.println("Unexpected result from oneCaseNoDefault: didn't get {\"Foo\"}"); 11.246 + } 11.247 + 11.248 + return failures; 11.249 + } 11.250 + 11.251 + static int testBreakOnly() { 11.252 + int failures = 0; 11.253 + 11.254 + for(String word : words) { 11.255 + failures += oneCaseBreakOnly(word, emptyStringSet, true); 11.256 + failures += oneCaseDefaultBreakOnly(word, emptyStringSet, true); 11.257 + } 11.258 + 11.259 + return failures; 11.260 + } 11.261 + 11.262 + static int testExpressionEval() { 11.263 + String s = "a"; 11.264 + int errors = 2; 11.265 + 11.266 + System.out.println("Testing expression evaluation."); 11.267 + 11.268 + switch (s + s) { 11.269 + case "aa": 11.270 + errors = 0; 11.271 + break; 11.272 + 11.273 + case "aaaa": 11.274 + errors = 1; 11.275 + System.err.println("Suspected bad expression evaluation."); 11.276 + break; 11.277 + 11.278 + default: 11.279 + throw new RuntimeException("Should not reach here."); 11.280 + } 11.281 + return errors; 11.282 + } 11.283 + 11.284 + static final String FOO = "foo"; 11.285 + 11.286 + static final String[] words = {"baz", 11.287 + "quux", 11.288 + "wombat", 11.289 + "\u0ccc\u0012"}; // hash collision with "foo" 11.290 + 11.291 + final static Set<String> emptyStringSet = Collections.emptySet(); 11.292 + 11.293 + public static void main(String... args) { 11.294 + int failures = 0; 11.295 + 11.296 + failures += testNullBehavior(); 11.297 + failures += testZeroCases(); 11.298 + failures += testOneCaseNoDefault(); 11.299 + failures += testBreakOnly(); 11.300 + failures += testExpressionEval(); 11.301 + 11.302 + if (failures > 0) { 11.303 + throw new RuntimeException(); 11.304 + } 11.305 + } 11.306 +}
12.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 12.2 +++ b/test/tools/javac/StringsInSwitch/RSCL1.out Mon Nov 02 21:36:59 2009 -0800 12.3 @@ -0,0 +1,2 @@ 12.4 +RepeatedStringCaseLabels1.java:13:9: compiler.err.duplicate.case.label 12.5 +1 error
13.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 13.2 +++ b/test/tools/javac/StringsInSwitch/RSCL2.out Mon Nov 02 21:36:59 2009 -0800 13.3 @@ -0,0 +1,2 @@ 13.4 +RepeatedStringCaseLabels2.java:14:9: compiler.err.duplicate.case.label 13.5 +1 error
14.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 14.2 +++ b/test/tools/javac/StringsInSwitch/RepeatedStringCaseLabels1.java Mon Nov 02 21:36:59 2009 -0800 14.3 @@ -0,0 +1,17 @@ 14.4 +/* 14.5 + * @test /nodynamiccopyright/ 14.6 + * @bug 6827009 14.7 + * @summary Check for repeated string case labels. 14.8 + * @compile/fail -source 6 RepeatedStringCaseLabels1.java 14.9 + * @compile/fail/ref=RSCL1.out -XDstdout -XDrawDiagnostics RepeatedStringCaseLabels1.java 14.10 + */ 14.11 +class RepeatedStringCaseLabels1 { 14.12 + String m(String s) { 14.13 + switch(s) { 14.14 + case "Hello World": 14.15 + return(s); 14.16 + case "Hello" + " " + "World": 14.17 + return (s + s); 14.18 + } 14.19 + } 14.20 +}
15.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 15.2 +++ b/test/tools/javac/StringsInSwitch/RepeatedStringCaseLabels2.java Mon Nov 02 21:36:59 2009 -0800 15.3 @@ -0,0 +1,18 @@ 15.4 +/* 15.5 + * @test /nodynamiccopyright/ 15.6 + * @bug 6827009 15.7 + * @summary Check for repeated string case labels. 15.8 + * @compile/fail -source 6 RepeatedStringCaseLabels2.java 15.9 + * @compile/fail/ref=RSCL2.out -XDstdout -XDrawDiagnostics RepeatedStringCaseLabels2.java 15.10 + */ 15.11 +class RepeatedStringCaseLabels2 { 15.12 + String m(String s) { 15.13 + final String constant = "Hello" + " " + "World"; 15.14 + switch(s) { 15.15 + case "Hello World": 15.16 + return(s); 15.17 + case constant: 15.18 + return (s + s); 15.19 + } 15.20 + } 15.21 +}
16.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 16.2 +++ b/test/tools/javac/StringsInSwitch/StringSwitches.java Mon Nov 02 21:36:59 2009 -0800 16.3 @@ -0,0 +1,263 @@ 16.4 +/* 16.5 + * Copyright 2009 Sun Microsystems, Inc. All Rights Reserved. 16.6 + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 16.7 + * 16.8 + * This code is free software; you can redistribute it and/or modify it 16.9 + * under the terms of the GNU General Public License version 2 only, as 16.10 + * published by the Free Software Foundation. 16.11 + * 16.12 + * This code is distributed in the hope that it will be useful, but WITHOUT 16.13 + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 16.14 + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 16.15 + * version 2 for more details (a copy is included in the LICENSE file that 16.16 + * accompanied this code). 16.17 + * 16.18 + * You should have received a copy of the GNU General Public License version 16.19 + * 2 along with this work; if not, write to the Free Software Foundation, 16.20 + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 16.21 + * 16.22 + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, 16.23 + * CA 95054 USA or visit www.sun.com if you need additional information or 16.24 + * have any questions. 16.25 + */ 16.26 + 16.27 +/* 16.28 + * @test 16.29 + * @bug 6827009 16.30 + * @summary Positive tests for strings in switch. 16.31 + * @author Joseph D. Darcy 16.32 + */ 16.33 + 16.34 +public class StringSwitches { 16.35 + 16.36 + public static void main(String... args) { 16.37 + int failures = 0; 16.38 + 16.39 + failures += testPileup(); 16.40 + failures += testSwitchingTwoWays(); 16.41 + failures += testNamedBreak(); 16.42 + 16.43 + if (failures > 0) { 16.44 + throw new RuntimeException(); 16.45 + } 16.46 + } 16.47 + 16.48 + /* 16.49 + * A zero length string and all strings consisting only of the 16.50 + * zero character \u0000 have a hash code of zero. This method 16.51 + * maps such strings to the number of times \u0000 appears for 0 16.52 + * through 6 occurrences. 16.53 + */ 16.54 + private static int zeroHashes(String s) { 16.55 + int result = Integer.MAX_VALUE; 16.56 + switch(s) { 16.57 + case "": 16.58 + return 0; 16.59 + 16.60 + case "\u0000": 16.61 + result = 1; break; 16.62 + 16.63 + case "\u0000\u0000": 16.64 + return 2; 16.65 + 16.66 + case "\u0000\u0000\u0000": 16.67 + result = 3; break; 16.68 + 16.69 + case "\u0000\u0000\u0000\u0000": 16.70 + return 4; 16.71 + 16.72 + case "\u0000\u0000\u0000\u0000\u0000": 16.73 + result = 5; break; 16.74 + 16.75 + case "\u0000\u0000\u0000\u0000\u0000\u0000": 16.76 + return 6; 16.77 + 16.78 + default: 16.79 + result = -1; 16.80 + } 16.81 + return result; 16.82 + } 16.83 + 16.84 + private static int testPileup() { 16.85 + int failures = 0; 16.86 + String zero = ""; 16.87 + for(int i = 0; i <= 6; i++, zero += "\u0000") { 16.88 + int result = zeroHashes(zero); 16.89 + if (result != i) { 16.90 + failures++; 16.91 + System.err.printf("For string \"%s\" unexpectedly got %d instead of %d%n.", 16.92 + zero, result, i); 16.93 + } 16.94 + } 16.95 + 16.96 + if (zeroHashes("foo") != -1) { 16.97 + failures++; 16.98 + System.err.println("Failed to get -1 for input string."); 16.99 + } 16.100 + 16.101 + return failures; 16.102 + } 16.103 + 16.104 + /** 16.105 + * Verify that a switch on an enum and a switch with the same 16.106 + * structure on the string name of an enum compute equivalent 16.107 + * values. 16.108 + */ 16.109 + private static int testSwitchingTwoWays() { 16.110 + int failures = 0; 16.111 + 16.112 + for(MetaSynVar msv : MetaSynVar.values()) { 16.113 + int enumResult = enumSwitch(msv); 16.114 + int stringResult = stringSwitch(msv.name()); 16.115 + 16.116 + if (enumResult != stringResult) { 16.117 + failures++; 16.118 + System.err.printf("One value %s, computed 0x%x with the enum switch " + 16.119 + "and 0x%x with the string one.%n", 16.120 + msv, enumResult, stringResult); 16.121 + } 16.122 + } 16.123 + 16.124 + return failures; 16.125 + } 16.126 + 16.127 + private static enum MetaSynVar { 16.128 + FOO, 16.129 + BAR, 16.130 + BAZ, 16.131 + QUX, 16.132 + QUUX, 16.133 + QUUUX, 16.134 + MUMBLE, 16.135 + FOOBAR; 16.136 + } 16.137 + 16.138 + private static int enumSwitch(MetaSynVar msv) { 16.139 + int result = 0; 16.140 + switch(msv) { 16.141 + case FOO: 16.142 + result |= (1<<0); 16.143 + // fallthrough: 16.144 + 16.145 + case BAR: 16.146 + case BAZ: 16.147 + result |= (1<<1); 16.148 + break; 16.149 + 16.150 + default: 16.151 + switch(msv) { 16.152 + case QUX: 16.153 + result |= (1<<2); 16.154 + break; 16.155 + 16.156 + case QUUX: 16.157 + result |= (1<<3); 16.158 + 16.159 + default: 16.160 + result |= (1<<4); 16.161 + } 16.162 + result |= (1<<5); 16.163 + break; 16.164 + 16.165 + case MUMBLE: 16.166 + result |= (1<<6); 16.167 + return result; 16.168 + 16.169 + case FOOBAR: 16.170 + result |= (1<<7); 16.171 + break; 16.172 + } 16.173 + result |= (1<<8); 16.174 + return result; 16.175 + } 16.176 + 16.177 + private static int stringSwitch(String msvName) { 16.178 + int result = 0; 16.179 + switch(msvName) { 16.180 + case "FOO": 16.181 + result |= (1<<0); 16.182 + // fallthrough: 16.183 + 16.184 + case "BAR": 16.185 + case "BAZ": 16.186 + result |= (1<<1); 16.187 + break; 16.188 + 16.189 + default: 16.190 + switch(msvName) { 16.191 + case "QUX": 16.192 + result |= (1<<2); 16.193 + break; 16.194 + 16.195 + case "QUUX": 16.196 + result |= (1<<3); 16.197 + 16.198 + default: 16.199 + result |= (1<<4); 16.200 + } 16.201 + result |= (1<<5); 16.202 + break; 16.203 + 16.204 + case "MUMBLE": 16.205 + result |= (1<<6); 16.206 + return result; 16.207 + 16.208 + case "FOOBAR": 16.209 + result |= (1<<7); 16.210 + break; 16.211 + } 16.212 + result |= (1<<8); 16.213 + return result; 16.214 + } 16.215 + 16.216 + private static int testNamedBreak() { 16.217 + int failures = 0; 16.218 + String[] testStrings = {"a", "b", "c", "d", "e"}; 16.219 + int[] testExpected = { 0b101011, 0b101, 0b100001, 0b101000, 0b10000}; 16.220 + 16.221 + for(int i = 0; i < testStrings.length; i++) { 16.222 + int expected = testExpected[i]; 16.223 + int result = namedBreak(testStrings[i]); 16.224 + 16.225 + if (result != expected) { 16.226 + failures++; 16.227 + 16.228 + System.err.printf("On input %s, got %d instead of %d.%n", 16.229 + testStrings[i], result, expected); 16.230 + } 16.231 + } 16.232 + 16.233 + return failures; 16.234 + } 16.235 + 16.236 + private static int namedBreak(String s) { 16.237 + int result = 0; 16.238 + outer: switch(s) { 16.239 + case "a": 16.240 + case "b": 16.241 + case "c": 16.242 + result |= (1<<0); 16.243 + inner: switch(s + s) { 16.244 + case "aa": 16.245 + result |= (1<<1); 16.246 + break inner; 16.247 + 16.248 + case "cc": 16.249 + break outer; 16.250 + 16.251 + default: 16.252 + result |= (1<<2); 16.253 + return result; 16.254 + } 16.255 + 16.256 + case "d": 16.257 + result |= (1<<3); 16.258 + break outer; 16.259 + 16.260 + default: 16.261 + return result |= (1<<4); 16.262 + } 16.263 + result |= (1<<5); 16.264 + return result; 16.265 + } 16.266 +}