3140 } else { |
3145 } else { |
3141 cases.append(c); |
3146 cases.append(c); |
3142 } |
3147 } |
3143 } |
3148 } |
3144 return make.Switch(selector, cases.toList()); |
3149 return make.Switch(selector, cases.toList()); |
|
3150 } |
|
3151 |
|
3152 public JCTree visitStringSwitch(JCSwitch tree) { |
|
3153 List<JCCase> caseList = tree.getCases(); |
|
3154 int alternatives = caseList.size(); |
|
3155 |
|
3156 if (alternatives == 0) { // Strange but legal possibility |
|
3157 return make.at(tree.pos()).Exec(attr.makeNullCheck(tree.getExpression())); |
|
3158 } else { |
|
3159 /* |
|
3160 * The general approach used is to translate a single |
|
3161 * string switch statement into a series of two chained |
|
3162 * switch statements: the first a synthesized statement |
|
3163 * switching on the argument string's hash value and |
|
3164 * computing a string's position in the list of original |
|
3165 * case labels, if any, followed by a second switch on the |
|
3166 * computed integer value. The second switch has the same |
|
3167 * code structure as the original string switch statement |
|
3168 * except that the string case labels are replaced with |
|
3169 * positional integer constants starting at 0. |
|
3170 * |
|
3171 * The first switch statement can be thought of as an |
|
3172 * inlined map from strings to their position in the case |
|
3173 * label list. An alternate implementation would use an |
|
3174 * actual Map for this purpose, as done for enum switches. |
|
3175 * |
|
3176 * With some additional effort, it would be possible to |
|
3177 * use a single switch statement on the hash code of the |
|
3178 * argument, but care would need to be taken to preserve |
|
3179 * the proper control flow in the presence of hash |
|
3180 * collisions and other complications, such as |
|
3181 * fallthroughs. Switch statements with one or two |
|
3182 * alternatives could also be specially translated into |
|
3183 * if-then statements to omit the computation of the hash |
|
3184 * code. |
|
3185 * |
|
3186 * The generated code assumes that the hashing algorithm |
|
3187 * of String is the same in the compilation environment as |
|
3188 * in the environment the code will run in. The string |
|
3189 * hashing algorithm in the SE JDK has been unchanged |
|
3190 * since at least JDK 1.2. |
|
3191 */ |
|
3192 |
|
3193 ListBuffer<JCStatement> stmtList = new ListBuffer<JCStatement>(); |
|
3194 |
|
3195 // Map from String case labels to their original position in |
|
3196 // the list of case labels. |
|
3197 Map<String, Integer> caseLabelToPosition = |
|
3198 new LinkedHashMap<String, Integer>(alternatives + 1, 1.0f); |
|
3199 |
|
3200 // Map of hash codes to the string case labels having that hashCode. |
|
3201 Map<Integer, Set<String>> hashToString = |
|
3202 new LinkedHashMap<Integer, Set<String>>(alternatives + 1, 1.0f); |
|
3203 |
|
3204 int casePosition = 0; |
|
3205 for(JCCase oneCase : caseList) { |
|
3206 JCExpression expression = oneCase.getExpression(); |
|
3207 |
|
3208 if (expression != null) { // expression for a "default" case is null |
|
3209 String labelExpr = (String) expression.type.constValue(); |
|
3210 Integer mapping = caseLabelToPosition.put(labelExpr, casePosition); |
|
3211 assert mapping == null; |
|
3212 int hashCode = labelExpr.hashCode(); |
|
3213 |
|
3214 Set<String> stringSet = hashToString.get(hashCode); |
|
3215 if (stringSet == null) { |
|
3216 stringSet = new LinkedHashSet<String>(1, 1.0f); |
|
3217 stringSet.add(labelExpr); |
|
3218 hashToString.put(hashCode, stringSet); |
|
3219 } else { |
|
3220 boolean added = stringSet.add(labelExpr); |
|
3221 assert added; |
|
3222 } |
|
3223 } |
|
3224 casePosition++; |
|
3225 } |
|
3226 |
|
3227 // Synthesize a switch statement that has the effect of |
|
3228 // mapping from a string to the integer position of that |
|
3229 // string in the list of case labels. This is done by |
|
3230 // switching on the hashCode of the string followed by an |
|
3231 // if-then-else chain comparing the input for equality |
|
3232 // with all the case labels having that hash value. |
|
3233 |
|
3234 /* |
|
3235 * s$ = top of stack; |
|
3236 * tmp$ = -1; |
|
3237 * switch($s.hashCode()) { |
|
3238 * case caseLabel.hashCode: |
|
3239 * if (s$.equals("caseLabel_1") |
|
3240 * tmp$ = caseLabelToPosition("caseLabel_1"); |
|
3241 * else if (s$.equals("caseLabel_2")) |
|
3242 * tmp$ = caseLabelToPosition("caseLabel_2"); |
|
3243 * ... |
|
3244 * break; |
|
3245 * ... |
|
3246 * } |
|
3247 */ |
|
3248 |
|
3249 VarSymbol dollar_s = new VarSymbol(FINAL|SYNTHETIC, |
|
3250 names.fromString("s" + tree.pos + target.syntheticNameChar()), |
|
3251 syms.stringType, |
|
3252 currentMethodSym); |
|
3253 stmtList.append(make.at(tree.pos()).VarDef(dollar_s, tree.getExpression()).setType(dollar_s.type)); |
|
3254 |
|
3255 VarSymbol dollar_tmp = new VarSymbol(SYNTHETIC, |
|
3256 names.fromString("tmp" + tree.pos + target.syntheticNameChar()), |
|
3257 syms.intType, |
|
3258 currentMethodSym); |
|
3259 JCVariableDecl dollar_tmp_def = |
|
3260 (JCVariableDecl)make.VarDef(dollar_tmp, make.Literal(INT, -1)).setType(dollar_tmp.type); |
|
3261 dollar_tmp_def.init.type = dollar_tmp.type = syms.intType; |
|
3262 stmtList.append(dollar_tmp_def); |
|
3263 ListBuffer<JCCase> caseBuffer = ListBuffer.lb(); |
|
3264 // hashCode will trigger nullcheck on original switch expression |
|
3265 JCMethodInvocation hashCodeCall = makeCall(make.Ident(dollar_s), |
|
3266 names.hashCode, |
|
3267 List.<JCExpression>nil()).setType(syms.intType); |
|
3268 JCSwitch switch1 = make.Switch(hashCodeCall, |
|
3269 caseBuffer.toList()); |
|
3270 for(Map.Entry<Integer, Set<String>> entry : hashToString.entrySet()) { |
|
3271 int hashCode = entry.getKey(); |
|
3272 Set<String> stringsWithHashCode = entry.getValue(); |
|
3273 assert stringsWithHashCode.size() >= 1; |
|
3274 |
|
3275 JCStatement elsepart = null; |
|
3276 for(String caseLabel : stringsWithHashCode ) { |
|
3277 JCMethodInvocation stringEqualsCall = makeCall(make.Ident(dollar_s), |
|
3278 names.equals, |
|
3279 List.<JCExpression>of(make.Literal(caseLabel))); |
|
3280 elsepart = make.If(stringEqualsCall, |
|
3281 make.Exec(make.Assign(make.Ident(dollar_tmp), |
|
3282 make.Literal(caseLabelToPosition.get(caseLabel))). |
|
3283 setType(dollar_tmp.type)), |
|
3284 elsepart); |
|
3285 } |
|
3286 |
|
3287 ListBuffer<JCStatement> lb = ListBuffer.lb(); |
|
3288 JCBreak breakStmt = make.Break(null); |
|
3289 breakStmt.target = switch1; |
|
3290 lb.append(elsepart).append(breakStmt); |
|
3291 |
|
3292 caseBuffer.append(make.Case(make.Literal(hashCode), lb.toList())); |
|
3293 } |
|
3294 |
|
3295 switch1.cases = caseBuffer.toList(); |
|
3296 stmtList.append(switch1); |
|
3297 |
|
3298 // Make isomorphic switch tree replacing string labels |
|
3299 // with corresponding integer ones from the label to |
|
3300 // position map. |
|
3301 |
|
3302 ListBuffer<JCCase> lb = ListBuffer.lb(); |
|
3303 JCSwitch switch2 = make.Switch(make.Ident(dollar_tmp), lb.toList()); |
|
3304 for(JCCase oneCase : caseList ) { |
|
3305 // Rewire up old unlabeled break statements to the |
|
3306 // replacement switch being created. |
|
3307 patchTargets(oneCase, tree, switch2); |
|
3308 |
|
3309 boolean isDefault = (oneCase.getExpression() == null); |
|
3310 JCExpression caseExpr; |
|
3311 if (isDefault) |
|
3312 caseExpr = null; |
|
3313 else { |
|
3314 caseExpr = make.Literal(caseLabelToPosition.get((String)oneCase. |
|
3315 getExpression(). |
|
3316 type.constValue())); |
|
3317 } |
|
3318 |
|
3319 lb.append(make.Case(caseExpr, |
|
3320 oneCase.getStatements())); |
|
3321 } |
|
3322 |
|
3323 switch2.cases = lb.toList(); |
|
3324 stmtList.append(switch2); |
|
3325 |
|
3326 return make.Block(0L, stmtList.toList()); |
|
3327 } |
3145 } |
3328 } |
3146 |
3329 |
3147 public void visitNewArray(JCNewArray tree) { |
3330 public void visitNewArray(JCNewArray tree) { |
3148 tree.elemtype = translate(tree.elemtype); |
3331 tree.elemtype = translate(tree.elemtype); |
3149 for (List<JCExpression> t = tree.dims; t.tail != null; t = t.tail) |
3332 for (List<JCExpression> t = tree.dims; t.tail != null; t = t.tail) |