001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.dev.jdt;
017:
018: import com.google.gwt.core.ext.TreeLogger;
019: import com.google.gwt.core.ext.UnableToCompleteException;
020: import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
021: import com.google.gwt.dev.util.Empty;
022: import com.google.gwt.dev.util.log.ThreadLocalTreeLoggerProxy;
023:
024: import org.eclipse.jdt.core.compiler.CharOperation;
025: import org.eclipse.jdt.core.compiler.IProblem;
026: import org.eclipse.jdt.internal.compiler.CompilationResult;
027: import org.eclipse.jdt.internal.compiler.Compiler;
028: import org.eclipse.jdt.internal.compiler.DefaultErrorHandlingPolicies;
029: import org.eclipse.jdt.internal.compiler.ICompilerRequestor;
030: import org.eclipse.jdt.internal.compiler.IErrorHandlingPolicy;
031: import org.eclipse.jdt.internal.compiler.IProblemFactory;
032: import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
033: import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
034: import org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException;
035: import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
036: import org.eclipse.jdt.internal.compiler.env.INameEnvironment;
037: import org.eclipse.jdt.internal.compiler.env.NameEnvironmentAnswer;
038: import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
039: import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
040: import org.eclipse.jdt.internal.compiler.problem.DefaultProblemFactory;
041:
042: import java.util.HashMap;
043: import java.util.HashSet;
044: import java.util.Locale;
045: import java.util.Map;
046: import java.util.Set;
047:
048: /**
049: * A facade around the JDT compiler to manage on-demand compilation, caching
050: * smartly where possible.
051: */
052: public abstract class AbstractCompiler {
053:
054: /**
055: * Adapted to hook the processing of compilation unit declarations so as to be
056: * able to add additional compilation units based on the results of
057: * previously-compiled ones. Examples of cases where this is useful include
058: * classes referenced only from JSNI and <code>GWT.create</code>.
059: */
060: private class CompilerImpl extends Compiler {
061:
062: private Set<CompilationUnitDeclaration> cuds;
063:
064: public CompilerImpl(INameEnvironment environment,
065: IErrorHandlingPolicy policy,
066: Map<String, String> settings,
067: ICompilerRequestor requestor,
068: IProblemFactory problemFactory) {
069: super (environment, policy, settings, requestor,
070: problemFactory);
071: }
072:
073: @Override
074: public void compile(ICompilationUnit[] sourceUnits) {
075: super .compile(sourceUnits);
076: cuds = null;
077: }
078:
079: @Override
080: public void process(CompilationUnitDeclaration cud, int index) {
081: // super.process(cud, index);
082: {
083: this .parser.getMethodBodies(cud);
084:
085: // fault in fields & methods
086: if (cud.scope != null) {
087: cud.scope.faultInTypes();
088: }
089:
090: // verify inherited methods
091: if (cud.scope != null) {
092: cud.scope.verifyMethods(lookupEnvironment
093: .methodVerifier());
094: }
095:
096: // type checking
097: cud.resolve();
098:
099: // flow analysis
100: cud.analyseCode();
101:
102: // code generation
103: if (doGenerateBytes) {
104: cud.generateCode();
105: }
106:
107: // reference info
108: if (options.produceReferenceInfo && cud.scope != null) {
109: cud.scope.storeDependencyInfo();
110: }
111:
112: // refresh the total number of units known at this stage
113: cud.compilationResult.totalUnitsKnown = totalUnits;
114: }
115:
116: ICompilationUnit cu = cud.compilationResult.compilationUnit;
117: String loc = String.valueOf(cu.getFileName());
118: TreeLogger logger = threadLogger.branch(TreeLogger.SPAM,
119: "Scanning for additional dependencies: " + loc,
120: null);
121:
122: // Examine the cud for magic types.
123: //
124: String[] typeNames = doFindAdditionalTypesUsingJsni(logger,
125: cud);
126:
127: // Accept each new compilation unit.
128: //
129: for (int i = 0; i < typeNames.length; i++) {
130: String typeName = typeNames[i];
131: final String msg = "Need additional type '" + typeName
132: + "'";
133: logger.log(TreeLogger.SPAM, msg, null);
134:
135: // This causes the compiler to find the additional type, possibly
136: // winding its back to ask for the compilation unit from the source
137: // oracle.
138: //
139: char[][] chars = CharOperation.splitOn('.', typeName
140: .toCharArray());
141: lookupEnvironment.getType(chars);
142: }
143:
144: typeNames = doFindAdditionalTypesUsingRebinds(logger, cud);
145:
146: // Accept each new compilation unit, and check for instantiability
147: //
148: for (int i = 0; i < typeNames.length; i++) {
149: String typeName = typeNames[i];
150: final String msg = "Need additional type '" + typeName
151: + "'";
152: logger.log(TreeLogger.SPAM, msg, null);
153:
154: // This causes the compiler to find the additional type, possibly
155: // winding its back to ask for the compilation unit from the source
156: // oracle.
157: //
158: resolvePossiblyNestedType(typeName);
159: }
160:
161: // Optionally remember this cud.
162: //
163: if (cuds != null) {
164: cuds.add(cud);
165: }
166: }
167:
168: private void compile(ICompilationUnit[] units,
169: Set<CompilationUnitDeclaration> cuds) {
170: this .cuds = cuds;
171: compile(units);
172: }
173:
174: private ReferenceBinding resolvePossiblyNestedType(
175: String typeName) {
176: ReferenceBinding type = null;
177:
178: int p = typeName.indexOf('$');
179: if (p > 0) {
180: // resolve an outer type before trying to get the cached inner
181: String cupName = typeName.substring(0, p);
182: char[][] chars = CharOperation.splitOn('.', cupName
183: .toCharArray());
184: if (lookupEnvironment.getType(chars) != null) {
185: // outer class was found
186: chars = CharOperation.splitOn('.', typeName
187: .toCharArray());
188: type = lookupEnvironment.getCachedType(chars);
189: if (type == null) {
190: // no inner type; this is a pure failure
191: return null;
192: }
193: }
194: } else {
195: // just resolve the type straight out
196: char[][] chars = CharOperation.splitOn('.', typeName
197: .toCharArray());
198: type = lookupEnvironment.getType(chars);
199: }
200:
201: if (type != null) {
202: // found it
203: return type;
204: }
205:
206: // Assume that the last '.' should be '$' and try again.
207: //
208: p = typeName.lastIndexOf('.');
209: if (p >= 0) {
210: typeName = typeName.substring(0, p) + "$"
211: + typeName.substring(p + 1);
212: return resolvePossiblyNestedType(typeName);
213: }
214:
215: return null;
216: }
217: }
218:
219: private class ICompilerRequestorImpl implements ICompilerRequestor {
220:
221: public ICompilerRequestorImpl() {
222: }
223:
224: public void acceptResult(CompilationResult result) {
225: // Handle compilation errors.
226: //
227: IProblem[] errors = result.getErrors();
228:
229: if (errors != null && errors.length > 0) {
230: // Dump it to disk.
231: //
232: String fn = String.valueOf(result.compilationUnit
233: .getFileName());
234: String msg = "Errors in '" + fn + "'";
235: TreeLogger branch = getLogger().branch(
236: TreeLogger.ERROR, msg, null);
237:
238: for (int i = 0; i < errors.length; i++) {
239: IProblem error = errors[i];
240:
241: // Strip the initial code from each error.
242: //
243: msg = error.toString();
244: msg = msg.substring(msg.indexOf(' '));
245:
246: // Append 'Line #: msg' to the error message.
247: //
248: StringBuffer msgBuf = new StringBuffer();
249: int line = error.getSourceLineNumber();
250: if (line > 0) {
251: msgBuf.append("Line ");
252: msgBuf.append(line);
253: msgBuf.append(": ");
254: }
255: msgBuf.append(msg);
256: branch.log(TreeLogger.ERROR, msgBuf.toString(),
257: null);
258: }
259: }
260:
261: // Let the subclass do something with this if it wants to.
262: //
263: doAcceptResult(result);
264: }
265: }
266:
267: private class INameEnvironmentImpl implements INameEnvironment {
268:
269: public INameEnvironmentImpl() {
270: }
271:
272: public void cleanup() {
273: // intentionally blank
274: }
275:
276: public NameEnvironmentAnswer findType(char[] type, char[][] pkg) {
277: return findType(CharOperation.arrayConcat(pkg, type));
278: }
279:
280: public NameEnvironmentAnswer findType(char[][] compoundTypeName) {
281:
282: // Cache the answers to findType to prevent the creation of more
283: // CompilationUnitDeclarations than needed.
284: String qname = CharOperation.toString(compoundTypeName);
285: if (nameEnvironmentAnswerForTypeName.containsKey(qname)) {
286: return (nameEnvironmentAnswerForTypeName.get(qname));
287: }
288: TreeLogger logger = threadLogger.branch(TreeLogger.SPAM,
289: "Compiler is asking about '" + qname + "'", null);
290:
291: if (sourceOracle.isPackage(qname)) {
292: logger.log(TreeLogger.SPAM, "Found to be a package",
293: null);
294: return null;
295: }
296:
297: // Try to find the compiled type in the cache.
298: //
299: ByteCode byteCode = doGetByteCodeFromCache(logger, qname);
300: if (byteCode != null) {
301: // Return it as a binary type to JDT.
302: //
303: byte[] classBytes = byteCode.getBytes();
304: char[] loc = byteCode.getLocation().toCharArray();
305: try {
306: logger.log(TreeLogger.SPAM, "Found cached bytes",
307: null);
308: ClassFileReader cfr = new ClassFileReader(
309: classBytes, loc);
310: NameEnvironmentAnswer out = new NameEnvironmentAnswer(
311: cfr, null);
312: nameEnvironmentAnswerForTypeName.put(qname, out);
313: return out;
314: } catch (ClassFormatException e) {
315: // Bad bytecode in the cache. Remove it from the cache.
316: //
317: String msg = "Bad bytecode for '" + qname + "'";
318: compiler.problemReporter
319: .abortDueToInternalError(msg);
320: return null;
321: }
322: }
323:
324: // Didn't find it in the cache, so let's compile from source.
325: // Strip off the inner types, if any
326: //
327: int pos = qname.indexOf('$');
328: if (pos >= 0) {
329: qname = qname.substring(0, pos);
330: }
331: CompilationUnitProvider cup;
332: try {
333: cup = sourceOracle.findCompilationUnit(logger, qname);
334: if (cup != null) {
335: logger.log(TreeLogger.SPAM,
336: "Found type in compilation unit: "
337: + cup.getLocation(), null);
338: ICompilationUnitAdapter unit = new ICompilationUnitAdapter(
339: cup);
340: NameEnvironmentAnswer out = new NameEnvironmentAnswer(
341: unit, null);
342: nameEnvironmentAnswerForTypeName.put(qname, out);
343: return out;
344: } else {
345: logger.log(TreeLogger.SPAM, "Not a known type",
346: null);
347: return null;
348: }
349: } catch (UnableToCompleteException e) {
350: // It was found, but something went really wrong trying to get it.
351: //
352: String msg = "Error acquiring source for '" + qname
353: + "'";
354: compiler.problemReporter.abortDueToInternalError(msg);
355: return null;
356: }
357: }
358:
359: public boolean isPackage(char[][] parentPkg, char[] pkg) {
360: // In special cases where class bytes are asserted from the outside,
361: // a package can exist that the host doesn't know about. We have to
362: // do a special check for these cases.
363: //
364: final char[] pathChars = CharOperation.concatWith(
365: parentPkg, pkg, '.');
366: String packageName = String.valueOf(pathChars);
367: if (knownPackages.contains(packageName)) {
368: return true;
369: } else if (sourceOracle.isPackage(packageName)) {
370: // Grow our own list to spare calls into the host.
371: //
372: rememberPackage(packageName);
373: return true;
374: } else {
375: return false;
376: }
377: }
378: }
379:
380: protected final ThreadLocalTreeLoggerProxy threadLogger = new ThreadLocalTreeLoggerProxy();
381:
382: private final CompilerImpl compiler;
383:
384: private final boolean doGenerateBytes;
385:
386: private final Set<String> knownPackages = new HashSet<String>();
387:
388: private final Map<String, NameEnvironmentAnswer> nameEnvironmentAnswerForTypeName = new HashMap<String, NameEnvironmentAnswer>();
389:
390: private final SourceOracle sourceOracle;
391:
392: private final Map<String, ICompilationUnit> unitsByTypeName = new HashMap<String, ICompilationUnit>();
393:
394: protected AbstractCompiler(SourceOracle sourceOracle,
395: boolean doGenerateBytes) {
396: this .sourceOracle = sourceOracle;
397: this .doGenerateBytes = doGenerateBytes;
398: rememberPackage("");
399:
400: INameEnvironment env = new INameEnvironmentImpl();
401: IErrorHandlingPolicy pol = DefaultErrorHandlingPolicies
402: .proceedWithAllProblems();
403: IProblemFactory probFact = new DefaultProblemFactory(Locale
404: .getDefault());
405: ICompilerRequestor req = new ICompilerRequestorImpl();
406: Map<String, String> settings = new HashMap<String, String>();
407: settings.put(CompilerOptions.OPTION_LineNumberAttribute,
408: CompilerOptions.GENERATE);
409: settings.put(CompilerOptions.OPTION_SourceFileAttribute,
410: CompilerOptions.GENERATE);
411: /*
412: * Tricks like "boolean stopHere = true;" depend on this setting to work in
413: * hosted mode. In web mode, our compiler should optimize them out once we
414: * do real data flow.
415: */
416: settings.put(CompilerOptions.OPTION_PreserveUnusedLocal,
417: CompilerOptions.PRESERVE);
418: settings.put(CompilerOptions.OPTION_ReportDeprecation,
419: CompilerOptions.IGNORE);
420: settings.put(CompilerOptions.OPTION_LocalVariableAttribute,
421: CompilerOptions.GENERATE);
422: settings.put(CompilerOptions.OPTION_Compliance,
423: CompilerOptions.VERSION_1_5);
424: settings.put(CompilerOptions.OPTION_Source,
425: CompilerOptions.VERSION_1_5);
426: settings.put(CompilerOptions.OPTION_TargetPlatform,
427: CompilerOptions.VERSION_1_5);
428:
429: // This is needed by TypeOracleBuilder to parse metadata.
430: settings.put(CompilerOptions.OPTION_DocCommentSupport,
431: CompilerOptions.ENABLED);
432:
433: compiler = new CompilerImpl(env, pol, settings, req, probFact);
434: }
435:
436: public void invalidateUnitsInFiles(Set<String> fileNames,
437: Set<String> typeNames) {
438: // StandardSourceOracle has its own cache that needs to be cleared
439: // out. Short of modifying the interface SourceOracle to have an
440: // invalidateCups, this check is needed.
441: if (sourceOracle instanceof StandardSourceOracle) {
442: StandardSourceOracle sso = (StandardSourceOracle) sourceOracle;
443: sso.invalidateCups(typeNames);
444: }
445: for (String qname : typeNames) {
446: unitsByTypeName.remove(qname);
447: nameEnvironmentAnswerForTypeName.remove(qname);
448: }
449: }
450:
451: protected final CompilationUnitDeclaration[] compile(
452: TreeLogger logger, ICompilationUnit[] units) {
453: // Any additional compilation units that are found to be needed will be
454: // pulled in while procssing compilation units. See CompilerImpl.process().
455: //
456: TreeLogger oldLogger = threadLogger.push(logger);
457: try {
458: Set<CompilationUnitDeclaration> cuds = new HashSet<CompilationUnitDeclaration>();
459: compiler.compile(units, cuds);
460: int size = cuds.size();
461: CompilationUnitDeclaration[] cudArray = new CompilationUnitDeclaration[size];
462: return cuds.toArray(cudArray);
463: } finally {
464: threadLogger.pop(oldLogger);
465: }
466: }
467:
468: protected void doAcceptResult(CompilationResult result) {
469: // Do nothing by default.
470: //
471: }
472:
473: protected String[] doFindAdditionalTypesUsingJsni(
474: TreeLogger logger, CompilationUnitDeclaration cud) {
475: return Empty.STRINGS;
476: }
477:
478: protected String[] doFindAdditionalTypesUsingRebinds(
479: TreeLogger logger, CompilationUnitDeclaration cud) {
480: return Empty.STRINGS;
481: }
482:
483: /**
484: * Checks to see if we already have the bytecode definition of the requested
485: * type. By default we compile everything from source, so we never have it
486: * unless a subclass overrides this method.
487: */
488: protected ByteCode doGetByteCodeFromCache(TreeLogger logger,
489: String binaryTypeName) {
490: return null;
491: }
492:
493: /**
494: * Finds a compilation unit for the given type. This is often used to
495: * bootstrap compiles since during compiles, the compiler will directly ask
496: * the name environment internally, bypassing this call.
497: */
498: protected ICompilationUnit getCompilationUnitForType(
499: TreeLogger logger, String binaryTypeName)
500: throws UnableToCompleteException {
501:
502: // We really look for the topmost type rather than a nested type.
503: //
504: String top = stripNestedTypeNames(binaryTypeName);
505:
506: // Check the cache.
507: //
508: ICompilationUnit unit = unitsByTypeName.get(top);
509: if (unit != null) {
510: return unit;
511: }
512:
513: // Not cached, so actually look for it.
514: //
515: CompilationUnitProvider cup = sourceOracle.findCompilationUnit(
516: logger, top);
517: if (cup == null) {
518: // Could not find the starting type.
519: //
520: String s = "Unable to find compilation unit for type '"
521: + top + "'";
522: logger.log(TreeLogger.WARN, s, null);
523: throw new UnableToCompleteException();
524: }
525:
526: // Create a cup adapter and cache it.
527: //
528: unit = new ICompilationUnitAdapter(cup);
529: unitsByTypeName.put(top, unit);
530:
531: return unit;
532: }
533:
534: protected TreeLogger getLogger() {
535: return threadLogger;
536: }
537:
538: /**
539: * Causes the compilation service itself to recognize the specified package
540: * name (and all its parent packages), avoiding a call back into the host.
541: * This is useful as an optimization, but more importantly, it is useful to
542: * compile against bytecode that was pre-compiled to which we don't have the
543: * source. This ability is crucial bridging the gap between user-level and
544: * "dev" code in hosted mode for classes such as JavaScriptHost and
545: * ShellJavaScriptHost.
546: */
547: protected void rememberPackage(String packageName) {
548: int i = packageName.lastIndexOf('.');
549: if (i != -1) {
550: // Ensure the parent package is also created.
551: //
552: rememberPackage(packageName.substring(0, i));
553: }
554: knownPackages.add(packageName);
555: }
556:
557: protected ReferenceBinding resolvePossiblyNestedType(String typeName) {
558: return compiler.resolvePossiblyNestedType(typeName);
559: }
560:
561: SourceOracle getSourceOracle() {
562: return sourceOracle;
563: }
564:
565: private String stripNestedTypeNames(String binaryTypeName) {
566: int i = binaryTypeName.lastIndexOf('.');
567: if (i == -1) {
568: i = 0;
569: }
570: int j = binaryTypeName.indexOf('$', i);
571: if (j != -1) {
572: return binaryTypeName.substring(0, j);
573: } else {
574: return binaryTypeName;
575: }
576: }
577: }
|