mirror of
https://github.com/openjdk/jdk.git
synced 2025-09-20 11:04:34 +02:00
6827009: Project Coin: Strings in Switch
Reviewed-by: jjg, mcimadamore
This commit is contained in:
parent
b967181a3f
commit
54d7808702
16 changed files with 886 additions and 8 deletions
|
@ -110,6 +110,9 @@ public enum Source {
|
|||
}
|
||||
|
||||
/** Allow encoding errors, giving only warnings. */
|
||||
public boolean allowStringsInSwitch() {
|
||||
return compareTo(JDK1_7) >= 0;
|
||||
}
|
||||
public boolean allowEncodingErrors() {
|
||||
return compareTo(JDK1_6) < 0;
|
||||
}
|
||||
|
|
|
@ -115,6 +115,8 @@ public class Attr extends JCTree.Visitor {
|
|||
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 @@ public class Attr extends JCTree.Visitor {
|
|||
*/
|
||||
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 @@ public class Attr extends JCTree.Visitor {
|
|||
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 @@ public class Attr extends JCTree.Visitor {
|
|||
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 {
|
||||
|
|
|
@ -357,7 +357,7 @@ public class Lower extends TreeTranslator {
|
|||
* 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 @@ public class Lower extends TreeTranslator {
|
|||
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 @@ public class Lower extends TreeTranslator {
|
|||
// 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 @@ public class Lower extends TreeTranslator {
|
|||
|
||||
// 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 @@ public class Lower extends TreeTranslator {
|
|||
}
|
||||
// where
|
||||
/**
|
||||
* A statment of the form
|
||||
* A statement of the form
|
||||
*
|
||||
* <pre>
|
||||
* for ( T v : arrayexpr ) stmt;
|
||||
|
@ -3109,12 +3109,17 @@ public class Lower extends TreeTranslator {
|
|||
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 @@ public class Lower extends TreeTranslator {
|
|||
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)
|
||||
|
|
|
@ -433,6 +433,8 @@ compiler.err.stack.sim.error=\
|
|||
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 @@ compiler.err.diamond.not.supported.in.source=\
|
|||
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.
|
||||
|
|
|
@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
BadlyTypedLabel1.java:13:14: compiler.err.prob.found.req: (compiler.misc.incompatible.types), int, java.lang.String
|
||||
1 error
|
|
@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
NonConstantLabel.java:14:14: compiler.err.string.const.req
|
||||
1 error
|
303
langtools/test/tools/javac/StringsInSwitch/OneCaseSwitches.java
Normal file
303
langtools/test/tools/javac/StringsInSwitch/OneCaseSwitches.java
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
2
langtools/test/tools/javac/StringsInSwitch/RSCL1.out
Normal file
2
langtools/test/tools/javac/StringsInSwitch/RSCL1.out
Normal file
|
@ -0,0 +1,2 @@
|
|||
RepeatedStringCaseLabels1.java:13:9: compiler.err.duplicate.case.label
|
||||
1 error
|
2
langtools/test/tools/javac/StringsInSwitch/RSCL2.out
Normal file
2
langtools/test/tools/javac/StringsInSwitch/RSCL2.out
Normal file
|
@ -0,0 +1,2 @@
|
|||
RepeatedStringCaseLabels2.java:14:9: compiler.err.duplicate.case.label
|
||||
1 error
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
263
langtools/test/tools/javac/StringsInSwitch/StringSwitches.java
Normal file
263
langtools/test/tools/javac/StringsInSwitch/StringSwitches.java
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue