src/share/classes/com/sun/tools/javac/comp/Lower.java
Print this page
@@ -355,11 +355,11 @@
* switch(Outer$0.$EnumMap$Color[colorExpression.ordinal()]) {
* case 1: stmt1;
* case 2: stmt2
* }
* </pre>
- * with the auxilliary table intialized as follows:
+ * with the auxiliary table intialized as follows:
* <pre>
* class Outer$0 {
* synthetic final int[] $EnumMap$Color = new int[Color.values().length];
* static {
* try { $EnumMap$Color[red.ordinal()] = 1; } catch (NoSuchFieldError ex) {}
@@ -3107,16 +3107,22 @@
public void visitSwitch(JCSwitch tree) {
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);
+ patchTargets(result, tree, result);
} else {
result = tree;
}
}
@@ -3142,10 +3148,188 @@
}
}
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)
if (t.head != null) t.head = translate(t.head, syms.intType);
tree.elems = translate(tree.elems, types.elemtype(tree.type));