KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > dev > jjs > impl > CastNormalizer


1 /*
2  * Copyright 2007 Google Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5  * use this file except in compliance with the License. You may obtain a copy of
6  * the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13  * License for the specific language governing permissions and limitations under
14  * the License.
15  */

16 package com.google.gwt.dev.jjs.impl;
17
18 import com.google.gwt.dev.jjs.ast.Context;
19 import com.google.gwt.dev.jjs.ast.JArrayRef;
20 import com.google.gwt.dev.jjs.ast.JArrayType;
21 import com.google.gwt.dev.jjs.ast.JBinaryOperation;
22 import com.google.gwt.dev.jjs.ast.JBinaryOperator;
23 import com.google.gwt.dev.jjs.ast.JCastOperation;
24 import com.google.gwt.dev.jjs.ast.JClassType;
25 import com.google.gwt.dev.jjs.ast.JExpression;
26 import com.google.gwt.dev.jjs.ast.JInstanceOf;
27 import com.google.gwt.dev.jjs.ast.JIntLiteral;
28 import com.google.gwt.dev.jjs.ast.JMethod;
29 import com.google.gwt.dev.jjs.ast.JMethodCall;
30 import com.google.gwt.dev.jjs.ast.JModVisitor;
31 import com.google.gwt.dev.jjs.ast.JNullLiteral;
32 import com.google.gwt.dev.jjs.ast.JNullType;
33 import com.google.gwt.dev.jjs.ast.JPrimitiveType;
34 import com.google.gwt.dev.jjs.ast.JProgram;
35 import com.google.gwt.dev.jjs.ast.JReferenceType;
36 import com.google.gwt.dev.jjs.ast.JType;
37 import com.google.gwt.dev.jjs.ast.JTypeOracle;
38 import com.google.gwt.dev.jjs.ast.JVisitor;
39 import com.google.gwt.dev.jjs.ast.js.JClassSeed;
40 import com.google.gwt.dev.jjs.ast.js.JsonObject;
41 import com.google.gwt.dev.jjs.ast.js.JsonObject.JsonPropInit;
42
43 import java.util.ArrayList JavaDoc;
44 import java.util.HashSet JavaDoc;
45 import java.util.IdentityHashMap JavaDoc;
46 import java.util.Iterator JavaDoc;
47 import java.util.List JavaDoc;
48 import java.util.Map JavaDoc;
49 import java.util.Set JavaDoc;
50
51 /**
52  * Replace cast and instanceof operations with calls to the Cast class. Depends
53  * on {@link com.google.gwt.dev.jjs.impl.CatchBlockNormalizer},
54  * {@link com.google.gwt.dev.jjs.impl.CompoundAssignmentNormalizer}, and
55  * {@link com.google.gwt.dev.jjs.impl.JavaScriptObjectCaster} having already
56  * run.
57  */

58 public class CastNormalizer {
59
60   private class AssignTypeIdsVisitor extends JVisitor {
61
62     Set JavaDoc/* <JClassType> */alreadyRan = new HashSet JavaDoc/* <JClassType> */();
63     private Map JavaDoc/* <JReferenceType, Set<JReferenceType>> */queriedTypes = new IdentityHashMap JavaDoc();
64     private int nextQueryId = 1; // 0 is reserved
65
private final List JavaDoc/* <JArrayType> */instantiatedArrayTypes = new ArrayList JavaDoc/* <JArrayType> */();
66     private List JavaDoc/* <JClassType> */classes = new ArrayList JavaDoc/* <JClassType> */();
67     private List JavaDoc/* <JsonObject> */jsonObjects = new ArrayList JavaDoc/* <JsonObject> */();
68
69     {
70       JTypeOracle typeOracle = program.typeOracle;
71       for (Iterator JavaDoc it = program.getAllArrayTypes().iterator(); it.hasNext();) {
72         JArrayType arrayType = (JArrayType) it.next();
73         if (typeOracle.isInstantiatedType(arrayType)) {
74           instantiatedArrayTypes.add(arrayType);
75         }
76       }
77     }
78
79     public void computeTypeIds() {
80
81       // the 0th entry is the "always false" entry
82
classes.add(null);
83       jsonObjects.add(new JsonObject(program));
84
85       /*
86        * Compute the list of classes than can successfully satisfy cast
87        * requests, along with the set of types they can be successfully cast to.
88        * Do it in super type order.
89        */

90       for (Iterator JavaDoc it = program.getDeclaredTypes().iterator(); it.hasNext();) {
91         JReferenceType type = (JReferenceType) it.next();
92         if (type instanceof JClassType) {
93           computeSourceClass((JClassType) type);
94         }
95       }
96
97       for (Iterator JavaDoc it = program.getAllArrayTypes().iterator(); it.hasNext();) {
98         JArrayType type = (JArrayType) it.next();
99         computeSourceClass(type);
100       }
101
102       // pass our info to JProgram
103
program.initTypeInfo(classes, jsonObjects);
104       program.recordQueryIds(queryIds);
105     }
106
107     /*
108      * If this expression could possibly generate an ArrayStoreException, we
109      * must record a query on the element type being assigned to.
110      */

111     // @Override
112
public void endVisit(JBinaryOperation x, Context ctx) {
113       if (x.getOp() == JBinaryOperator.ASG && x.getLhs() instanceof JArrayRef) {
114
115         // first, calculate the transitive closure of all possible runtime types
116
// the lhs could be
117
JExpression instance = ((JArrayRef) x.getLhs()).getInstance();
118         if (instance.getType() instanceof JNullType) {
119           // will generate a null pointer exception instead
120
return;
121         }
122         JArrayType lhsArrayType = (JArrayType) instance.getType();
123         JType elementType = lhsArrayType.getElementType();
124
125         // primitives are statically correct
126
if (!(elementType instanceof JReferenceType)) {
127           return;
128         }
129
130         // element type being final means the assignment is statically correct
131
if (((JReferenceType) elementType).isFinal()) {
132           return;
133         }
134
135         /*
136          * For every instantiated array type that could -in theory- be the
137          * runtime type of the lhs, we must record a cast from the rhs to the
138          * prospective element type of the lhs.
139          */

140         JTypeOracle typeOracle = program.typeOracle;
141         JType rhsType = x.getRhs().getType();
142         assert (rhsType instanceof JReferenceType);
143         JReferenceType refRhsType = (JReferenceType) rhsType;
144         for (Iterator JavaDoc it = instantiatedArrayTypes.iterator(); it.hasNext();) {
145           JArrayType arrayType = (JArrayType) it.next();
146           if (typeOracle.canTheoreticallyCast(arrayType, lhsArrayType)) {
147             JType itElementType = arrayType.getElementType();
148             if (itElementType instanceof JReferenceType) {
149               recordCastInternal((JReferenceType) itElementType, refRhsType);
150             }
151           }
152         }
153       }
154     }
155
156     // @Override
157
public void endVisit(JCastOperation x, Context ctx) {
158       if (x.getCastType() != program.getTypeNull()) {
159         recordCast(x.getCastType(), x.getExpr());
160       }
161     }
162
163     // @Override
164
public void endVisit(JInstanceOf x, Context ctx) {
165       assert (x.getTestType() != program.getTypeNull());
166       recordCast(x.getTestType(), x.getExpr());
167     }
168
169     /**
170      * Create the data for JSON table to capture the mapping from a class to its
171      * query types.
172      */

173     private void computeSourceClass(JClassType type) {
174       if (type == null || alreadyRan.contains(type)) {
175         return;
176       }
177
178       alreadyRan.add(type);
179
180       /*
181        * IMPORTANT: Visit my supertype first. The implementation of
182        * com.google.gwt.lang.Cast.wrapJSO() depends on all superclasses having
183        * typeIds that are less than all their subclasses. This allows the same
184        * JSO to be wrapped stronger but not weaker.
185        */

186       computeSourceClass(type.extnds);
187
188       if (!program.typeOracle.isInstantiatedType(type)) {
189         return;
190       }
191
192       // Find all possible query types which I can satisfy
193
Set JavaDoc/* <JReferenceType> */yesSet = null;
194       for (Iterator JavaDoc iter = queriedTypes.keySet().iterator(); iter.hasNext();) {
195         JReferenceType qType = (JReferenceType) iter.next();
196         Set JavaDoc/* <JReferenceType> */querySet = (Set JavaDoc) queriedTypes.get(qType);
197         if (program.typeOracle.canTriviallyCast(type, qType)) {
198           for (Iterator JavaDoc it = querySet.iterator(); it.hasNext();) {
199             JReferenceType argType = (JReferenceType) it.next();
200             if (program.typeOracle.canTriviallyCast(type, argType)) {
201               if (yesSet == null) {
202                 yesSet = new HashSet JavaDoc/* <JReferenceType> */();
203               }
204               yesSet.add(qType);
205               break;
206             }
207           }
208         }
209       }
210
211       /*
212        * Weird: JavaScriptObjects MUST have a typeId, the implementation of
213        * Cast.wrapJSO depends on it.
214        */

215       if (yesSet == null && !program.isJavaScriptObject(type)) {
216         return; // won't satisfy anything
217
}
218
219       // use an array to sort my yes set
220
JReferenceType[] yesArray = new JReferenceType[nextQueryId];
221       if (yesSet != null) {
222         for (Iterator JavaDoc it = yesSet.iterator(); it.hasNext();) {
223           JReferenceType yesType = (JReferenceType) it.next();
224           Integer JavaDoc boxedInt = (Integer JavaDoc) queryIds.get(yesType);
225           yesArray[boxedInt.intValue()] = yesType;
226         }
227       }
228
229       // create a sparse lookup object
230
JsonObject jsonObject = new JsonObject(program);
231       for (int i = 0; i < nextQueryId; ++i) {
232         if (yesArray[i] != null) {
233           JIntLiteral labelExpr = program.getLiteralInt(i);
234           JIntLiteral valueExpr = program.getLiteralInt(1);
235           jsonObject.propInits.add(new JsonPropInit(program, labelExpr,
236               valueExpr));
237         }
238       }
239
240       // add an entry for me
241
classes.add(type);
242       jsonObjects.add(jsonObject);
243     }
244
245     private void recordCast(JType targetType, JExpression rhs) {
246       if (targetType instanceof JReferenceType) {
247         // unconditional cast b/c it would've been a semantic error earlier
248
JReferenceType rhsType = (JReferenceType) rhs.getType();
249         // don't record a type for trivial casts that won't generate code
250
if (rhsType instanceof JClassType) {
251           if (program.typeOracle.canTriviallyCast(rhsType,
252               (JReferenceType) targetType)) {
253             return;
254           }
255         }
256
257         recordCastInternal((JReferenceType) targetType, rhsType);
258       }
259     }
260
261     private void recordCastInternal(JReferenceType targetType,
262         JReferenceType rhsType) {
263       JReferenceType toType = targetType;
264       Set JavaDoc/* <JReferenceType> */querySet = (Set JavaDoc) queriedTypes.get(toType);
265       if (querySet == null) {
266         queryIds.put(toType, new Integer JavaDoc(nextQueryId++));
267         querySet = new HashSet JavaDoc/* <JReferenceType> */();
268         queriedTypes.put(toType, querySet);
269       }
270       querySet.add(rhsType);
271     }
272   }
273
274   /**
275    * Explicitly convert any char-typed expressions within a concat operation
276    * into strings.
277    */

278   private class ConcatVisitor extends JModVisitor {
279
280     private JMethod stringValueOfChar = null;
281
282     // @Override
283
public void endVisit(JBinaryOperation x, Context ctx) {
284       if (x.getType() != program.getTypeJavaLangString()) {
285         return;
286       }
287
288       if (x.getOp() == JBinaryOperator.ADD) {
289         JExpression newLhs = convertCharString(x.getLhs());
290         JExpression newRhs = convertCharString(x.getRhs());
291         if (newLhs != x.getLhs() || newRhs != x.getRhs()) {
292           JBinaryOperation newExpr = new JBinaryOperation(program,
293               x.getSourceInfo(), program.getTypeJavaLangString(),
294               JBinaryOperator.ADD, newLhs, newRhs);
295           ctx.replaceMe(newExpr);
296         }
297       } else if (x.getOp() == JBinaryOperator.ASG_ADD) {
298         JExpression newRhs = convertCharString(x.getRhs());
299         if (newRhs != x.getRhs()) {
300           JBinaryOperation newExpr = new JBinaryOperation(program,
301               x.getSourceInfo(), program.getTypeJavaLangString(),
302               JBinaryOperator.ASG_ADD, x.getLhs(), newRhs);
303           ctx.replaceMe(newExpr);
304         }
305       }
306     }
307
308     private JExpression convertCharString(JExpression expr) {
309       JPrimitiveType charType = program.getTypePrimitiveChar();
310       if (expr.getType() == charType) {
311         // Replace the character with a call to Cast.charToString()
312
if (stringValueOfChar == null) {
313           stringValueOfChar = program.getSpecialMethod("Cast.charToString");
314           assert (stringValueOfChar != null);
315         }
316         JMethodCall call = new JMethodCall(program, expr.getSourceInfo(), null,
317             stringValueOfChar);
318         call.getArgs().add(expr);
319         return call;
320       }
321       return expr;
322     }
323   }
324
325   /**
326    * Explicitly cast all integral divide operations to trigger replacements with
327    * narrowing calls in the next pass.
328    */

329   private class DivVisitor extends JModVisitor {
330
331     // @Override
332
public void endVisit(JBinaryOperation x, Context ctx) {
333       JType type = x.getType();
334       if (x.getOp() == JBinaryOperator.DIV
335           && type != program.getTypePrimitiveFloat()
336           && type != program.getTypePrimitiveDouble()) {
337         x.setType(program.getTypePrimitiveDouble());
338         JCastOperation cast = new JCastOperation(program, x.getSourceInfo(),
339             type, x);
340         ctx.replaceMe(cast);
341       }
342     }
343   }
344
345   /**
346    * Replaces all casts and instanceof operations with calls to implementation
347    * methods.
348    */

349   private class ReplaceTypeChecksVisitor extends JModVisitor {
350
351     // @Override
352
public void endVisit(JCastOperation x, Context ctx) {
353       JExpression replaceExpr;
354       JType toType = x.getCastType();
355       if (toType instanceof JNullType) {
356         /*
357          * Magic: a null type cast means the user tried a cast that couldn't
358          * possibly work. Typically this means either the statically resolvable
359          * arg type is incompatible with the target type, or the target type was
360          * globally uninstantiable. We handle this cast by throwing a
361          * ClassCastException, unless the argument is null.
362          */

363         JMethod method = program.getSpecialMethod("Cast.throwClassCastExceptionUnlessNull");
364         /*
365          * Override the type of the magic method with the null type.
366          */

367         JMethodCall call = new JMethodCall(program, x.getSourceInfo(), null,
368             method, program.getTypeNull());
369         call.getArgs().add(x.getExpr());
370         replaceExpr = call;
371       } else if (toType instanceof JReferenceType) {
372         JExpression curExpr = x.getExpr();
373         JReferenceType refType = (JReferenceType) toType;
374         JType argType = x.getExpr().getType();
375         if (program.isJavaScriptObject(argType)) {
376           /*
377            * A JSO-derived class that is about to be cast must be "wrapped"
378            * first. Since a JSO was never constructed, it may not have an
379            * accessible prototype. Instead we copy fields from the seed
380            * function's prototype directly onto the target object as expandos.
381            * See com.google.gwt.lang.Cast.wrapJSO().
382            */

383           JMethod wrap = program.getSpecialMethod("Cast.wrapJSO");
384           // override the type of the called method with the JSO's type
385
JMethodCall call = new JMethodCall(program, x.getSourceInfo(), null,
386               wrap, argType);
387           JClassSeed seed = program.getLiteralClassSeed((JClassType) argType);
388           call.getArgs().add(curExpr);
389           call.getArgs().add(seed);
390           curExpr = call;
391         }
392         if (argType instanceof JClassType
393             && program.typeOracle.canTriviallyCast((JClassType) argType,
394                 refType)) {
395           // TODO(???): why is this only for JClassType?
396
// just remove the cast
397
replaceExpr = curExpr;
398         } else {
399           JMethod method = program.getSpecialMethod("Cast.dynamicCast");
400           // override the type of the called method with the target cast type
401
JMethodCall call = new JMethodCall(program, x.getSourceInfo(), null,
402               method, toType);
403           Integer JavaDoc boxedInt = (Integer JavaDoc) queryIds.get(refType);
404           JIntLiteral qId = program.getLiteralInt(boxedInt.intValue());
405           call.getArgs().add(curExpr);
406           call.getArgs().add(qId);
407           replaceExpr = call;
408         }
409       } else {
410         /*
411          * See JLS 5.1.3: if a cast narrows from one type to another, we must
412          * call a narrowing conversion function. EXCEPTION: we currently have no
413          * way to narrow double to float, so don't bother.
414          */

415         boolean narrow = false, round = false;
416         JPrimitiveType tByte = program.getTypePrimitiveByte();
417         JPrimitiveType tChar = program.getTypePrimitiveChar();
418         JPrimitiveType tShort = program.getTypePrimitiveShort();
419         JPrimitiveType tInt = program.getTypePrimitiveInt();
420         JPrimitiveType tLong = program.getTypePrimitiveLong();
421         JPrimitiveType tFloat = program.getTypePrimitiveFloat();
422         JPrimitiveType tDouble = program.getTypePrimitiveDouble();
423         JType fromType = x.getExpr().getType();
424         if (tByte == fromType) {
425           if (tChar == toType) {
426             narrow = true;
427           }
428         } else if (tShort == fromType) {
429           if (tByte == toType || tChar == toType) {
430             narrow = true;
431           }
432         } else if (tChar == fromType) {
433           if (tByte == toType || tShort == toType) {
434             narrow = true;
435           }
436         } else if (tInt == fromType) {
437           if (tByte == toType || tShort == toType || tChar == toType) {
438             narrow = true;
439           }
440         } else if (tLong == fromType) {
441           if (tByte == toType || tShort == toType || tChar == toType
442               || tInt == toType) {
443             narrow = true;
444           }
445         } else if (tFloat == fromType || tDouble == fromType) {
446           if (tByte == toType || tShort == toType || tChar == toType
447               || tInt == toType || tLong == toType) {
448             round = true;
449           }
450         }
451
452         if (narrow || round) {
453           // Replace the expression with a call to the narrow or round method
454
String JavaDoc methodName = "Cast." + (narrow ? "narrow_" : "round_")
455               + toType.getName();
456           JMethod castMethod = program.getSpecialMethod(methodName);
457           JMethodCall call = new JMethodCall(program, x.getSourceInfo(), null,
458               castMethod);
459           call.getArgs().add(x.getExpr());
460           replaceExpr = call;
461         } else {
462           // Just remove the cast
463
replaceExpr = x.getExpr();
464         }
465       }
466       ctx.replaceMe(replaceExpr);
467     }
468
469     // @Override
470
public void endVisit(JInstanceOf x, Context ctx) {
471       JType argType = x.getExpr().getType();
472       if (argType instanceof JClassType
473           && program.typeOracle.canTriviallyCast((JClassType) argType,
474               x.getTestType())) {
475         // trivially true if non-null; replace with a null test
476
JNullLiteral nullLit = program.getLiteralNull();
477         JBinaryOperation eq = new JBinaryOperation(program, x.getSourceInfo(),
478             program.getTypePrimitiveBoolean(), JBinaryOperator.NEQ,
479             x.getExpr(), nullLit);
480         ctx.replaceMe(eq);
481       } else {
482         JMethod method = program.getSpecialMethod("Cast.instanceOf");
483         JMethodCall call = new JMethodCall(program, x.getSourceInfo(), null,
484             method);
485         Integer JavaDoc boxedInt = (Integer JavaDoc) queryIds.get(x.getTestType());
486         JIntLiteral qId = program.getLiteralInt(boxedInt.intValue());
487         call.getArgs().add(x.getExpr());
488         call.getArgs().add(qId);
489         ctx.replaceMe(call);
490       }
491     }
492   }
493
494   public static void exec(JProgram program) {
495     new CastNormalizer(program).execImpl();
496   }
497
498   private Map JavaDoc/* <JReferenceType, Integer> */queryIds = new IdentityHashMap JavaDoc();
499
500   private final JProgram program;
501
502   private CastNormalizer(JProgram program) {
503     this.program = program;
504   }
505
506   private void execImpl() {
507     {
508       ConcatVisitor visitor = new ConcatVisitor();
509       visitor.accept(program);
510     }
511     {
512       DivVisitor visitor = new DivVisitor();
513       visitor.accept(program);
514     }
515     {
516       AssignTypeIdsVisitor assigner = new AssignTypeIdsVisitor();
517       assigner.accept(program);
518       assigner.computeTypeIds();
519     }
520     {
521       ReplaceTypeChecksVisitor replacer = new ReplaceTypeChecksVisitor();
522       replacer.accept(program);
523     }
524   }
525
526 }
527
Popular Tags