KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > dev > jdt > AbstractCompiler


1 /*
2  * Copyright 2006 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.jdt;
17
18 import com.google.gwt.core.ext.TreeLogger;
19 import com.google.gwt.core.ext.UnableToCompleteException;
20 import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
21 import com.google.gwt.dev.util.Empty;
22 import com.google.gwt.dev.util.log.ThreadLocalTreeLoggerProxy;
23
24 import org.eclipse.jdt.core.compiler.CharOperation;
25 import org.eclipse.jdt.core.compiler.IProblem;
26 import org.eclipse.jdt.internal.compiler.CompilationResult;
27 import org.eclipse.jdt.internal.compiler.Compiler;
28 import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
29 import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
30 import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
31 import org.eclipse.jdt.internal.compiler.IProblemFactory;
32 import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
33 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
34 import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
35 import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
36 import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
37 import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
38 import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
39 import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
40 import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
41
42 import java.util.HashMap JavaDoc;
43 import java.util.HashSet JavaDoc;
44 import java.util.Iterator JavaDoc;
45 import java.util.Locale JavaDoc;
46 import java.util.Map JavaDoc;
47 import java.util.Set JavaDoc;
48
49 /**
50  * A facade around the JDT compiler to manage on-demand compilation, caching
51  * smartly where possible.
52  */

53 public abstract class AbstractCompiler {
54
55   /**
56    * Adapted to hook the processing of compilation unit declarations so as to be
57    * able to add additional compilation units based on the results of
58    * previously-compiled ones. Examples of cases where this is useful include
59    * classes referenced only from JSNI and <code>GWT.create</code>.
60    */

61   private class CompilerImpl extends Compiler JavaDoc {
62
63     public HashSet JavaDoc resolved = new HashSet JavaDoc();
64
65     private Set JavaDoc cuds;
66
67     public CompilerImpl(INameEnvironment environment,
68         IErrorHandlingPolicy policy, Map JavaDoc settings,
69         ICompilerRequestor requestor, IProblemFactory problemFactory) {
70       super(environment, policy, settings, requestor, problemFactory);
71     }
72
73     public void compile(ICompilationUnit[] sourceUnits) {
74       super.compile(sourceUnits);
75       cuds = null;
76     }
77
78     public void process(CompilationUnitDeclaration cud, int index) {
79       // super.process(cud, index);
80
{
81         this.parser.getMethodBodies(cud);
82
83         // fault in fields & methods
84
if (cud.scope != null) {
85           cud.scope.faultInTypes();
86         }
87
88         // verify inherited methods
89
if (cud.scope != null) {
90           cud.scope.verifyMethods(lookupEnvironment.methodVerifier());
91         }
92
93         // type checking
94
cud.resolve();
95
96         // flow analysis
97
cud.analyseCode();
98
99         // code generation
100
if (doGenerateBytes) {
101           cud.generateCode();
102         }
103
104         // reference info
105
if (options.produceReferenceInfo && cud.scope != null) {
106           cud.scope.storeDependencyInfo();
107         }
108
109         // refresh the total number of units known at this stage
110
cud.compilationResult.totalUnitsKnown = totalUnits;
111       }
112
113       ICompilationUnit cu = cud.compilationResult.compilationUnit;
114       String JavaDoc loc = String.valueOf(cu.getFileName());
115       TreeLogger logger = threadLogger.branch(TreeLogger.SPAM,
116           "Scanning for additional dependencies: " + loc, null);
117
118       // Examine the cud for magic types.
119
//
120
String JavaDoc[] typeNames = doFindAdditionalTypesUsingJsni(logger, cud);
121
122       // Accept each new compilation unit.
123
//
124
for (int i = 0; i < typeNames.length; i++) {
125         String JavaDoc typeName = typeNames[i];
126         final String JavaDoc msg = "Need additional type '" + typeName + "'";
127         logger.log(TreeLogger.SPAM, msg, null);
128
129         // This causes the compiler to find the additional type, possibly
130
// winding its back to ask for the compilation unit from the source
131
// oracle.
132
//
133
char[][] chars = CharOperation.splitOn('.', typeName.toCharArray());
134         lookupEnvironment.getType(chars);
135       }
136
137       typeNames = doFindAdditionalTypesUsingRebinds(logger, cud);
138
139       // Accept each new compilation unit, and check for instantiability
140
//
141
for (int i = 0; i < typeNames.length; i++) {
142         String JavaDoc typeName = typeNames[i];
143         final String JavaDoc msg = "Need additional type '" + typeName + "'";
144         logger.log(TreeLogger.SPAM, msg, null);
145
146         // This causes the compiler to find the additional type, possibly
147
// winding its back to ask for the compilation unit from the source
148
// oracle.
149
//
150
ReferenceBinding type = resolvePossiblyNestedType(typeName);
151       }
152
153       // Optionally remember this cud.
154
//
155
if (cuds != null) {
156         cuds.add(cud);
157       }
158     }
159
160     private void compile(ICompilationUnit[] units, Set JavaDoc cuds) {
161       this.cuds = cuds;
162       compile(units);
163     }
164
165     private ReferenceBinding resolvePossiblyNestedType(String JavaDoc typeName) {
166       ReferenceBinding type = null;
167
168       int p = typeName.indexOf('$');
169       if (p > 0) {
170         // resolve an outer type before trying to get the cached inner
171
String JavaDoc cupName = typeName.substring(0, p);
172         char[][] chars = CharOperation.splitOn('.', cupName.toCharArray());
173         if (lookupEnvironment.getType(chars) != null) {
174           // outer class was found
175
chars = CharOperation.splitOn('.', typeName.toCharArray());
176           type = lookupEnvironment.getCachedType(chars);
177           if (type == null) {
178             // no inner type; this is a pure failure
179
return null;
180           }
181         }
182       } else {
183         // just resolve the type straight out
184
char[][] chars = CharOperation.splitOn('.', typeName.toCharArray());
185         type = lookupEnvironment.getType(chars);
186       }
187
188       if (type != null) {
189         // found it
190
return type;
191       }
192
193       // Assume that the last '.' should be '$' and try again.
194
//
195
p = typeName.lastIndexOf('.');
196       if (p >= 0) {
197         typeName = typeName.substring(0, p) + "$" + typeName.substring(p + 1);
198         return resolvePossiblyNestedType(typeName);
199       }
200
201       return null;
202     }
203   }
204
205   private class ICompilerRequestorImpl implements ICompilerRequestor {
206
207     public ICompilerRequestorImpl() {
208     }
209
210     public void acceptResult(CompilationResult result) {
211       // Handle compilation errors.
212
//
213
IProblem[] errors = result.getErrors();
214
215       if (errors != null && errors.length > 0) {
216         // Dump it to disk.
217
//
218
String JavaDoc fn = String.valueOf(result.compilationUnit.getFileName());
219         String JavaDoc msg = "Errors in '" + fn + "'";
220         TreeLogger branch = getLogger().branch(TreeLogger.ERROR, msg, null);
221
222         for (int i = 0; i < errors.length; i++) {
223           IProblem error = errors[i];
224
225           // Strip the initial code from each error.
226
//
227
msg = error.toString();
228           msg = msg.substring(msg.indexOf(' '));
229
230           if (error.getID() >= IProblem.InvalidUsageOfTypeParameters
231               && error.getID() <= IProblem.InvalidUsageOfAnnotationDeclarations) {
232             // this error involves 5.0 compliance, use a custom message
233
msg = "GWT does not yet support the Java 5.0 language enhancements; only 1.4 compatible source may be used";
234           }
235
236           // Append 'Line #: msg' to the error message.
237
//
238
StringBuffer JavaDoc msgBuf = new StringBuffer JavaDoc();
239           int line = error.getSourceLineNumber();
240           if (line > 0) {
241             msgBuf.append("Line ");
242             msgBuf.append(line);
243             msgBuf.append(": ");
244           }
245           msgBuf.append(msg);
246           branch.log(TreeLogger.ERROR, msgBuf.toString(), null);
247         }
248       }
249
250       // Let the subclass do something with this if it wants to.
251
//
252
doAcceptResult(result);
253     }
254   }
255
256   private class INameEnvironmentImpl implements INameEnvironment {
257
258     public INameEnvironmentImpl() {
259     }
260
261     public void cleanup() {
262       // intentionally blank
263
}
264
265     public NameEnvironmentAnswer findType(char[] type, char[][] pkg) {
266       return findType(CharOperation.arrayConcat(pkg, type));
267     }
268
269     public NameEnvironmentAnswer findType(char[][] compoundTypeName) {
270
271       // Cache the answers to findType to prevent the creation of more
272
// CompilationUnitDeclarations than needed.
273
String JavaDoc qname = CharOperation.toString(compoundTypeName);
274       if (nameEnvironmentAnswerForTypeName.containsKey(qname)) {
275         return (NameEnvironmentAnswer) (nameEnvironmentAnswerForTypeName.get(qname));
276       }
277       TreeLogger logger = threadLogger.branch(TreeLogger.SPAM,
278           "Compiler is asking about '" + qname + "'", null);
279
280       if (sourceOracle.isPackage(qname)) {
281         logger.log(TreeLogger.SPAM, "Found to be a package", null);
282         return null;
283       }
284
285       // Try to find the compiled type in the cache.
286
//
287
ByteCode byteCode = doGetByteCodeFromCache(logger, qname);
288       if (byteCode != null) {
289         // Return it as a binary type to JDT.
290
//
291
byte[] classBytes = byteCode.getBytes();
292         char[] loc = byteCode.getLocation().toCharArray();
293         try {
294           logger.log(TreeLogger.SPAM, "Found cached bytes", null);
295           ClassFileReader cfr = new ClassFileReader(classBytes, loc);
296           NameEnvironmentAnswer out = new NameEnvironmentAnswer(cfr, null);
297           nameEnvironmentAnswerForTypeName.put(qname, out);
298           return out;
299         } catch (ClassFormatException e) {
300           // Bad bytecode in the cache. Remove it from the cache.
301
//
302
String JavaDoc msg = "Bad bytecode for '" + qname + "'";
303           compiler.problemReporter.abortDueToInternalError(msg);
304           return null;
305         }
306       }
307
308       // Didn't find it in the cache, so let's compile from source.
309
// Strip off the inner types, if any
310
//
311
int pos = qname.indexOf('$');
312       if (pos >= 0) {
313         qname = qname.substring(0, pos);
314       }
315       CompilationUnitProvider cup;
316       try {
317         cup = sourceOracle.findCompilationUnit(logger, qname);
318         if (cup != null) {
319           logger.log(TreeLogger.SPAM, "Found type in compilation unit: "
320               + cup.getLocation(), null);
321           ICompilationUnitAdapter unit = new ICompilationUnitAdapter(cup);
322           NameEnvironmentAnswer out = new NameEnvironmentAnswer(unit, null);
323           nameEnvironmentAnswerForTypeName.put(qname, out);
324           return out;
325         } else {
326           logger.log(TreeLogger.SPAM, "Not a known type", null);
327           return null;
328         }
329       } catch (UnableToCompleteException e) {
330         // It was found, but something went really wrong trying to get it.
331
//
332
String JavaDoc msg = "Error acquiring source for '" + qname + "'";
333         compiler.problemReporter.abortDueToInternalError(msg);
334         return null;
335       }
336     }
337
338     public boolean isPackage(char[][] parentPkg, char[] pkg) {
339       // In special cases where class bytes are asserted from the outside,
340
// a package can exist that the host doesn't know about. We have to
341
// do a special check for these cases.
342
//
343
final char[] pathChars = CharOperation.concatWith(parentPkg, pkg, '.');
344       String JavaDoc packageName = String.valueOf(pathChars);
345       if (knownPackages.contains(packageName)) {
346         return true;
347       } else if (sourceOracle.isPackage(packageName)) {
348         // Grow our own list to spare calls into the host.
349
//
350
rememberPackage(packageName);
351         return true;
352       } else {
353         return false;
354       }
355     }
356   }
357
358   private final CompilerImpl compiler;
359
360   private final boolean doGenerateBytes;
361
362   private final Set JavaDoc knownPackages = new HashSet JavaDoc();
363
364   private final Map JavaDoc nameEnvironmentAnswerForTypeName = new HashMap JavaDoc();
365
366   private final SourceOracle sourceOracle;
367
368   private final ThreadLocalTreeLoggerProxy threadLogger = new ThreadLocalTreeLoggerProxy();
369
370   private final Map JavaDoc unitsByTypeName = new HashMap JavaDoc();
371
372   protected AbstractCompiler(SourceOracle sourceOracle, boolean doGenerateBytes) {
373     this.sourceOracle = sourceOracle;
374     this.doGenerateBytes = doGenerateBytes;
375     rememberPackage("");
376
377     INameEnvironment env = new INameEnvironmentImpl();
378     IErrorHandlingPolicy pol = DefaultErrorHandlingPolicies.proceedWithAllProblems();
379     IProblemFactory probFact = new DefaultProblemFactory(Locale.getDefault());
380     ICompilerRequestor req = new ICompilerRequestorImpl();
381     Map JavaDoc settings = new HashMap JavaDoc();
382     settings.put(CompilerOptions.OPTION_LineNumberAttribute,
383         CompilerOptions.GENERATE);
384     settings.put(CompilerOptions.OPTION_SourceFileAttribute,
385         CompilerOptions.GENERATE);
386     /*
387      * Tricks like "boolean stopHere = true;" depend on this setting to work in
388      * hosted mode. In web mode, our compiler should optimize them out once we
389      * do real data flow.
390      */

391     settings.put(CompilerOptions.OPTION_PreserveUnusedLocal,
392         CompilerOptions.PRESERVE);
393     settings.put(CompilerOptions.OPTION_ReportDeprecation,
394         CompilerOptions.IGNORE);
395     settings.put(CompilerOptions.OPTION_LocalVariableAttribute,
396         CompilerOptions.GENERATE);
397     settings.put(CompilerOptions.OPTION_Source, CompilerOptions.VERSION_1_4);
398     settings.put(CompilerOptions.OPTION_TargetPlatform,
399         CompilerOptions.VERSION_1_4);
400
401     // This is needed by TypeOracleBuilder to parse metadata.
402
settings.put(CompilerOptions.OPTION_DocCommentSupport,
403         CompilerOptions.ENABLED);
404
405     compiler = new CompilerImpl(env, pol, settings, req, probFact);
406   }
407
408   public void invalidateUnitsInFiles(Set JavaDoc fileNames, Set JavaDoc typeNames) {
409     // StandardSourceOracle has its own cache that needs to be cleared
410
// out. Short of modifying the interface SourceOracle to have an
411
// invalidateCups, this check is needed.
412
if (sourceOracle instanceof StandardSourceOracle) {
413       StandardSourceOracle sso = (StandardSourceOracle) sourceOracle;
414       sso.invalidateCups(typeNames);
415     }
416     for (Iterator JavaDoc iter = typeNames.iterator(); iter.hasNext();) {
417       String JavaDoc qname = (String JavaDoc) iter.next();
418       unitsByTypeName.remove(qname);
419       nameEnvironmentAnswerForTypeName.remove(qname);
420     }
421   }
422
423   protected final CompilationUnitDeclaration[] compile(TreeLogger logger,
424       ICompilationUnit[] units) {
425     // Any additional compilation units that are found to be needed will be
426
// pulled in while procssing compilation units. See CompilerImpl.process().
427
//
428
threadLogger.set(logger);
429     Set JavaDoc cuds = new HashSet JavaDoc();
430     compiler.compile(units, cuds);
431     int size = cuds.size();
432     CompilationUnitDeclaration[] cudArray = new CompilationUnitDeclaration[size];
433     return (CompilationUnitDeclaration[]) cuds.toArray(cudArray);
434   }
435
436   protected void doAcceptResult(CompilationResult result) {
437     // Do nothing by default.
438
//
439
}
440
441   protected String JavaDoc[] doFindAdditionalTypesUsingJsni(TreeLogger logger,
442       CompilationUnitDeclaration cud) {
443     return Empty.STRINGS;
444   }
445
446   protected String JavaDoc[] doFindAdditionalTypesUsingRebinds(TreeLogger logger,
447       CompilationUnitDeclaration cud) {
448     return Empty.STRINGS;
449   }
450
451   /**
452    * Checks to see if we already have the bytecode definition of the requested
453    * type. By default we compile everything from source, so we never have it
454    * unless a subclass overrides this method.
455    */

456   protected ByteCode doGetByteCodeFromCache(TreeLogger logger,
457       String JavaDoc binaryTypeName) {
458     return null;
459   }
460
461   /**
462    * Finds a compilation unit for the given type. This is often used to
463    * bootstrap compiles since during compiles, the compiler will directly ask
464    * the name environment internally, bypassing this call.
465    */

466   protected ICompilationUnit getCompilationUnitForType(TreeLogger logger,
467       String JavaDoc binaryTypeName) throws UnableToCompleteException {
468
469     // We really look for the topmost type rather than a nested type.
470
//
471
String JavaDoc top = stripNestedTypeNames(binaryTypeName);
472
473     // Check the cache.
474
//
475
ICompilationUnit unit = (ICompilationUnit) unitsByTypeName.get(top);
476     if (unit != null) {
477       return unit;
478     }
479
480     // Not cached, so actually look for it.
481
//
482
CompilationUnitProvider cup = sourceOracle.findCompilationUnit(logger, top);
483     if (cup == null) {
484       // Could not find the starting type.
485
//
486
String JavaDoc s = "Unable to find compilation unit for type '" + top + "'";
487       logger.log(TreeLogger.WARN, s, null);
488       throw new UnableToCompleteException();
489     }
490
491     // Create a cup adapter and cache it.
492
//
493
unit = new ICompilationUnitAdapter(cup);
494     unitsByTypeName.put(top, unit);
495
496     return unit;
497   }
498
499   protected TreeLogger getLogger() {
500     return threadLogger;
501   }
502
503   /**
504    * Causes the compilation service itself to recognize the specified package
505    * name (and all its parent packages), avoiding a call back into the host.
506    * This is useful as an optimization, but more importantly, it is useful to
507    * compile against bytecode that was pre-compiled to which we don't have the
508    * source. This ability is crucial bridging the gap between user-level and
509    * "dev" code in hosted mode for classes such as JavaScriptHost and
510    * ShellJavaScriptHost.
511    */

512   protected void rememberPackage(String JavaDoc packageName) {
513     int i = packageName.lastIndexOf('.');
514     if (i != -1) {
515       // Ensure the parent package is also created.
516
//
517
rememberPackage(packageName.substring(0, i));
518     }
519     knownPackages.add(packageName);
520   }
521
522   protected ReferenceBinding resolvePossiblyNestedType(String JavaDoc typeName) {
523     return compiler.resolvePossiblyNestedType(typeName);
524   }
525
526   protected void setLogger(TreeLogger logger) {
527     threadLogger.set(logger);
528   }
529
530   SourceOracle getSourceOracle() {
531     return sourceOracle;
532   }
533
534   private String JavaDoc stripNestedTypeNames(String JavaDoc binaryTypeName) {
535     int i = binaryTypeName.lastIndexOf('.');
536     if (i == -1) {
537       i = 0;
538     }
539     int j = binaryTypeName.indexOf('$', i);
540     if (j != -1) {
541       return binaryTypeName.substring(0, j);
542     } else {
543       return binaryTypeName;
544     }
545   }
546 }
547
Popular Tags