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.jjs;
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.jdt.ICompilationUnitAdapter;
022: import com.google.gwt.dev.jdt.RebindOracle;
023: import com.google.gwt.dev.jdt.RebindPermutationOracle;
024: import com.google.gwt.dev.jdt.WebModeCompilerFrontEnd;
025: import com.google.gwt.dev.jjs.InternalCompilerException.NodeInfo;
026: import com.google.gwt.dev.jjs.ast.JClassType;
027: import com.google.gwt.dev.jjs.ast.JExpression;
028: import com.google.gwt.dev.jjs.ast.JMethod;
029: import com.google.gwt.dev.jjs.ast.JMethodBody;
030: import com.google.gwt.dev.jjs.ast.JMethodCall;
031: import com.google.gwt.dev.jjs.ast.JNewInstance;
032: import com.google.gwt.dev.jjs.ast.JProgram;
033: import com.google.gwt.dev.jjs.ast.JReferenceType;
034: import com.google.gwt.dev.jjs.impl.ArrayNormalizer;
035: import com.google.gwt.dev.jjs.impl.AssertionRemover;
036: import com.google.gwt.dev.jjs.impl.BuildTypeMap;
037: import com.google.gwt.dev.jjs.impl.CastNormalizer;
038: import com.google.gwt.dev.jjs.impl.CatchBlockNormalizer;
039: import com.google.gwt.dev.jjs.impl.CompoundAssignmentNormalizer;
040: import com.google.gwt.dev.jjs.impl.DeadCodeElimination;
041: import com.google.gwt.dev.jjs.impl.GenerateJavaAST;
042: import com.google.gwt.dev.jjs.impl.GenerateJavaScriptAST;
043: import com.google.gwt.dev.jjs.impl.JavaScriptObjectCaster;
044: import com.google.gwt.dev.jjs.impl.MakeCallsStatic;
045: import com.google.gwt.dev.jjs.impl.MethodAndClassFinalizer;
046: import com.google.gwt.dev.jjs.impl.MethodCallTightener;
047: import com.google.gwt.dev.jjs.impl.MethodInliner;
048: import com.google.gwt.dev.jjs.impl.Pruner;
049: import com.google.gwt.dev.jjs.impl.ReplaceRebinds;
050: import com.google.gwt.dev.jjs.impl.TypeMap;
051: import com.google.gwt.dev.jjs.impl.TypeTightener;
052: import com.google.gwt.dev.js.JsInliner;
053: import com.google.gwt.dev.js.JsNormalizer;
054: import com.google.gwt.dev.js.JsObfuscateNamer;
055: import com.google.gwt.dev.js.JsPrettyNamer;
056: import com.google.gwt.dev.js.JsSourceGenerationVisitor;
057: import com.google.gwt.dev.js.JsStringInterner;
058: import com.google.gwt.dev.js.JsSymbolResolver;
059: import com.google.gwt.dev.js.JsUnusedFunctionRemover;
060: import com.google.gwt.dev.js.JsVerboseNamer;
061: import com.google.gwt.dev.js.ast.JsProgram;
062: import com.google.gwt.dev.util.DefaultTextOutput;
063: import com.google.gwt.dev.util.Util;
064:
065: import org.eclipse.jdt.core.compiler.IProblem;
066: import org.eclipse.jdt.internal.compiler.CompilationResult;
067: import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
068: import org.eclipse.jdt.internal.compiler.ast.TypeDeclaration;
069:
070: import java.util.HashSet;
071: import java.util.List;
072: import java.util.Set;
073: import java.util.TreeSet;
074:
075: /**
076: * Compiles the Java <code>JProgram</code> representation into its
077: * corresponding JavaScript source.
078: */
079: public class JavaToJavaScriptCompiler {
080:
081: private static void findEntryPoints(TreeLogger logger,
082: RebindOracle rebindOracle, String[] mainClassNames,
083: JProgram program) throws UnableToCompleteException {
084: JMethod bootStrapMethod = program.createMethod(null, "init"
085: .toCharArray(), null, program.getTypeVoid(), false,
086: true, true, false, false);
087: bootStrapMethod.freezeParamTypes();
088:
089: for (int i = 0; i < mainClassNames.length; ++i) {
090: String mainClassName = mainClassNames[i];
091: JReferenceType referenceType = program
092: .getFromTypeMap(mainClassName);
093:
094: if (referenceType == null) {
095: logger.log(TreeLogger.ERROR,
096: "Could not find module entry point class '"
097: + mainClassName + "'", null);
098: throw new UnableToCompleteException();
099: }
100:
101: JExpression qualifier = null;
102: JMethod mainMethod = findMainMethod(referenceType);
103: if (mainMethod == null || !mainMethod.isStatic()) {
104: // Couldn't find a static main method; must rebind the class
105: String originalClassName = mainClassName;
106: mainClassName = rebindOracle.rebind(logger,
107: originalClassName);
108: referenceType = program.getFromTypeMap(mainClassName);
109: if (referenceType == null) {
110: logger.log(TreeLogger.ERROR,
111: "Could not find module entry point class '"
112: + mainClassName
113: + "' after rebinding from '"
114: + originalClassName + "'", null);
115: throw new UnableToCompleteException();
116: }
117:
118: if (!(referenceType instanceof JClassType)) {
119: logger.log(TreeLogger.ERROR,
120: "Module entry point class '"
121: + mainClassName
122: + "' must be a class", null);
123: throw new UnableToCompleteException();
124: }
125:
126: JClassType mainClass = (JClassType) referenceType;
127: if (mainClass.isAbstract()) {
128: logger.log(TreeLogger.ERROR,
129: "Module entry point class '"
130: + mainClassName
131: + "' must not be abstract", null);
132: throw new UnableToCompleteException();
133: }
134:
135: mainMethod = findMainMethodRecurse(referenceType);
136: if (mainMethod == null) {
137: logger.log(TreeLogger.ERROR,
138: "Could not find entry method 'onModuleLoad()' method in entry point class '"
139: + mainClassName + "'", null);
140: throw new UnableToCompleteException();
141: }
142:
143: if (mainMethod.isAbstract()) {
144: logger.log(TreeLogger.ERROR,
145: "Entry method 'onModuleLoad' in entry point class '"
146: + mainClassName
147: + "' must not be abstract", null);
148: throw new UnableToCompleteException();
149: }
150:
151: if (!mainMethod.isStatic()) {
152: // Find the appropriate (noArg) constructor
153: JMethod noArgCtor = null;
154: for (int j = 0; j < mainClass.methods.size(); ++j) {
155: JMethod ctor = mainClass.methods.get(j);
156: if (ctor.getName().equals(
157: mainClass.getShortName())) {
158: if (ctor.params.size() == 0) {
159: noArgCtor = ctor;
160: }
161: }
162: }
163: if (noArgCtor == null) {
164: logger
165: .log(
166: TreeLogger.ERROR,
167: "No default (zero argument) constructor could be found in entry point class '"
168: + mainClassName
169: + "' to qualify a call to non-static entry method 'onModuleLoad'",
170: null);
171: throw new UnableToCompleteException();
172: }
173:
174: // Construct a new instance of the class to qualify the non-static
175: // call
176: JNewInstance newInstance = new JNewInstance(
177: program, null, mainClass);
178: qualifier = new JMethodCall(program, null,
179: newInstance, noArgCtor);
180: }
181: }
182:
183: // qualifier will be null if onModuleLoad is static
184: JMethodCall onModuleLoadCall = new JMethodCall(program,
185: null, qualifier, mainMethod);
186: JMethodBody body = (JMethodBody) bootStrapMethod.getBody();
187: body.getStatements().add(onModuleLoadCall.makeStatement());
188: }
189: program.addEntryMethod(bootStrapMethod);
190: }
191:
192: private static JMethod findMainMethod(JReferenceType referenceType) {
193: for (int j = 0; j < referenceType.methods.size(); ++j) {
194: JMethod method = referenceType.methods.get(j);
195: if (method.getName().equals("onModuleLoad")) {
196: if (method.params.size() == 0) {
197: return method;
198: }
199: }
200: }
201: return null;
202: }
203:
204: private static JMethod findMainMethodRecurse(
205: JReferenceType referenceType) {
206: for (JReferenceType it = referenceType; it != null; it = it.extnds) {
207: JMethod result = findMainMethod(it);
208: if (result != null) {
209: return result;
210: }
211: }
212: return null;
213: }
214:
215: private final String[] declEntryPoints;
216: private final CompilationUnitDeclaration[] goldenCuds;
217: private long lastModified;
218: private final JJSOptions options;
219: private final Set<IProblem> problemSet = new HashSet<IProblem>();
220:
221: public JavaToJavaScriptCompiler(TreeLogger logger,
222: WebModeCompilerFrontEnd compiler, String[] declEntryPts)
223: throws UnableToCompleteException {
224: this (logger, compiler, declEntryPts, new JJSOptions());
225: }
226:
227: public JavaToJavaScriptCompiler(TreeLogger logger,
228: WebModeCompilerFrontEnd compiler, String[] declEntryPts,
229: JJSOptions compilerOptions)
230: throws UnableToCompleteException {
231:
232: if (declEntryPts.length == 0) {
233: throw new IllegalArgumentException(
234: "entry point(s) required");
235: }
236:
237: this .options = new JJSOptions(compilerOptions);
238:
239: // Remember these for subsequent compiles.
240: //
241: this .declEntryPoints = declEntryPts;
242:
243: if (!options.isValidateOnly()) {
244: // Find all the possible rebound entry points.
245: RebindPermutationOracle rpo = compiler
246: .getRebindPermutationOracle();
247: Set<String> allEntryPoints = new TreeSet<String>();
248: for (String element : declEntryPts) {
249: String[] all = rpo.getAllPossibleRebindAnswers(logger,
250: element);
251: Util.addAll(allEntryPoints, all);
252: }
253: allEntryPoints.add("com.google.gwt.lang.Array");
254: allEntryPoints.add("com.google.gwt.lang.Cast");
255: allEntryPoints.add("com.google.gwt.lang.Exceptions");
256: allEntryPoints.add("java.lang.Object");
257: allEntryPoints.add("java.lang.String");
258: allEntryPoints.add("java.lang.Iterable");
259: declEntryPts = allEntryPoints.toArray(new String[0]);
260: }
261:
262: // Compile the source and get the compiler so we can get the parse tree
263: //
264: goldenCuds = compiler.getCompilationUnitDeclarations(logger,
265: declEntryPts);
266:
267: // Check for compilation problems. We don't log here because any problems
268: // found here will have already been logged by AbstractCompiler.
269: //
270: checkForErrors(logger, false);
271:
272: // Find the newest of all these.
273: //
274: lastModified = 0;
275: CompilationUnitProvider newestCup = null;
276: for (CompilationUnitDeclaration cud : goldenCuds) {
277: ICompilationUnitAdapter icua = (ICompilationUnitAdapter) cud.compilationResult.compilationUnit;
278: CompilationUnitProvider cup = icua
279: .getCompilationUnitProvider();
280: long cupLastModified = cup.getLastModified();
281: if (cupLastModified > lastModified) {
282: newestCup = cup;
283: lastModified = cupLastModified;
284: }
285: }
286: if (newestCup != null) {
287: String loc = newestCup.getLocation();
288: String msg = "Newest compilation unit is '" + loc + "'";
289: logger.log(TreeLogger.DEBUG, msg, null);
290: }
291: }
292:
293: /**
294: * Creates finished JavaScript source code from the specified Java compilation
295: * units.
296: */
297: public String compile(TreeLogger logger, RebindOracle rebindOracle)
298: throws UnableToCompleteException {
299:
300: try {
301:
302: // (1) Build a flattened map of TypeDeclarations => JType.
303: //
304:
305: // Note that all reference types (even nested and local ones) are in the
306: // resulting type map. BuildTypeMap also parses all JSNI.
307: //
308: JProgram jprogram = new JProgram(logger, rebindOracle);
309: TypeMap typeMap = new TypeMap(jprogram);
310: JsProgram jsProgram = new JsProgram();
311: TypeDeclaration[] allTypeDeclarations = BuildTypeMap.exec(
312: typeMap, goldenCuds, jsProgram);
313:
314: // BuildTypeMap can uncover syntactic JSNI errors; report & abort
315: //
316: checkForErrors(logger, true);
317:
318: // Compute all super type/sub type info
319: jprogram.typeOracle.computeBeforeAST();
320:
321: // (3) Create a normalized Java AST using our own notation.
322: //
323:
324: // Create the tree from JDT
325: GenerateJavaAST.exec(allTypeDeclarations, typeMap,
326: jprogram, jsProgram);
327:
328: // GenerateJavaAST can uncover semantic JSNI errors; report & abort
329: //
330: checkForErrors(logger, true);
331:
332: // TODO: figure out how to have a debug mode.
333: boolean isDebugEnabled = false;
334: if (!isDebugEnabled) {
335: // Remove all assert statements.
336: AssertionRemover.exec(jprogram);
337: }
338:
339: // Fix up GWT.create() into new operations
340: ReplaceRebinds.exec(jprogram);
341:
342: if (options.isValidateOnly()) {
343: // That's it, we're done.
344: return null;
345: }
346:
347: // Rebind each entry point.
348: //
349: findEntryPoints(logger, rebindOracle, declEntryPoints,
350: jprogram);
351:
352: // (4) Optimize the normalized Java AST
353: boolean didChange;
354: do {
355: // Recompute clinits each time, they can become empty.
356: jprogram.typeOracle.recomputeClinits();
357:
358: didChange = false;
359: // Remove unreferenced types, fields, methods, [params, locals]
360: didChange = Pruner.exec(jprogram, true) || didChange;
361: // finalize locals, params, fields, methods, classes
362: didChange = MethodAndClassFinalizer.exec(jprogram)
363: || didChange;
364: // rewrite non-polymorphic calls as static calls; update all call sites
365: didChange = MakeCallsStatic.exec(jprogram) || didChange;
366:
367: // type flow tightening
368: // - fields, locals based on assignment
369: // - params based on assignment and call sites
370: // - method bodies based on return statements
371: // - polymorphic methods based on return types of all implementors
372: // - optimize casts and instance of
373: didChange = TypeTightener.exec(jprogram) || didChange;
374:
375: // tighten method call bindings
376: didChange = MethodCallTightener.exec(jprogram)
377: || didChange;
378:
379: // dead code removal??
380: didChange = DeadCodeElimination.exec(jprogram)
381: || didChange;
382:
383: if (options.isAggressivelyOptimize()) {
384: // inlining
385: didChange = MethodInliner.exec(jprogram)
386: || didChange;
387: }
388: // prove that any types that have been culled from the main tree are
389: // unreferenced due to type tightening?
390: } while (didChange);
391:
392: // (5) "Normalize" the high-level Java tree into a lower-level tree more
393: // suited for JavaScript code generation. Don't go reordering these
394: // willy-nilly because there are some subtle interdependencies.
395: if (isDebugEnabled) {
396: // AssertionNormalizer.exec(jprogram);
397: }
398: CatchBlockNormalizer.exec(jprogram);
399: CompoundAssignmentNormalizer.exec(jprogram);
400: JavaScriptObjectCaster.exec(jprogram);
401: CastNormalizer.exec(jprogram);
402: ArrayNormalizer.exec(jprogram);
403:
404: // (6) Perform further post-normalization optimizations
405: // Prune everything
406: Pruner.exec(jprogram, false);
407:
408: // (7) Generate a JavaScript code DOM from the Java type declarations
409: jprogram.typeOracle.recomputeClinits();
410: GenerateJavaScriptAST.exec(jprogram, jsProgram, options
411: .getOutput());
412:
413: // (8) Fix invalid constructs created during JS AST gen
414: JsNormalizer.exec(jsProgram);
415:
416: // (9) Resolve all unresolved JsNameRefs
417: JsSymbolResolver.exec(jsProgram);
418:
419: // (10) Apply optimizations to JavaScript AST
420: if (options.isAggressivelyOptimize()) {
421: do {
422: didChange = false;
423: // Inline JavaScript function invocations
424: didChange = options.isAggressivelyOptimize()
425: && JsInliner.exec(jsProgram) || didChange;
426: // Remove unused functions, possible
427: didChange = JsUnusedFunctionRemover.exec(jsProgram)
428: || didChange;
429: } while (didChange);
430: }
431:
432: // (11) Obfuscate
433: switch (options.getOutput()) {
434: case OBFUSCATED:
435: JsStringInterner.exec(jsProgram);
436: JsObfuscateNamer.exec(jsProgram);
437: break;
438: case PRETTY:
439: // We don't intern strings in pretty mode to improve readability
440: JsPrettyNamer.exec(jsProgram);
441: break;
442: case DETAILED:
443: JsStringInterner.exec(jsProgram);
444: JsVerboseNamer.exec(jsProgram);
445: break;
446: default:
447: throw new InternalCompilerException(
448: "Unknown output mode");
449: }
450:
451: DefaultTextOutput out = new DefaultTextOutput(options
452: .getOutput().shouldMinimize());
453: JsSourceGenerationVisitor v = new JsSourceGenerationVisitor(
454: out);
455: v.accept(jsProgram);
456: return out.toString();
457: } catch (UnableToCompleteException e) {
458: // just rethrow
459: throw e;
460: } catch (InternalCompilerException e) {
461: TreeLogger topBranch = logger.branch(TreeLogger.ERROR,
462: "An internal compiler exception occurred", e);
463: List<NodeInfo> nodeTrace = e.getNodeTrace();
464: for (NodeInfo nodeInfo : nodeTrace) {
465: SourceInfo info = nodeInfo.getSourceInfo();
466: String msg;
467: if (info != null) {
468: String fileName = info.getFileName();
469: fileName = fileName.substring(fileName
470: .lastIndexOf('/') + 1);
471: fileName = fileName.substring(fileName
472: .lastIndexOf('\\') + 1);
473: msg = "at " + fileName + "(" + info.getStartLine()
474: + "): ";
475: } else {
476: msg = "<no source info>: ";
477: }
478:
479: String description = nodeInfo.getDescription();
480: if (description != null) {
481: msg += description;
482: } else {
483: msg += "<no description available>";
484: }
485: TreeLogger nodeBranch = topBranch.branch(
486: TreeLogger.ERROR, msg, null);
487: String className = nodeInfo.getClassName();
488: if (className != null) {
489: nodeBranch.log(TreeLogger.INFO, className, null);
490: }
491: }
492: throw new UnableToCompleteException();
493: } catch (Throwable e) {
494: logger.log(TreeLogger.ERROR,
495: "Unexpected internal compiler error", e);
496: throw new UnableToCompleteException();
497: }
498: }
499:
500: public long getLastModifiedTimeOfNewestCompilationUnit() {
501: return lastModified;
502: }
503:
504: private void checkForErrors(final TreeLogger logger,
505: boolean itemizeErrors) throws UnableToCompleteException {
506: boolean compilationFailed = false;
507: if (goldenCuds.length == 0) {
508: compilationFailed = true;
509: }
510: for (CompilationUnitDeclaration cud : goldenCuds) {
511: CompilationResult result = cud.compilationResult();
512: if (result.hasErrors()) {
513: compilationFailed = true;
514: // Early out if we don't need to itemize.
515: if (!itemizeErrors) {
516: break;
517: }
518: TreeLogger branch = logger.branch(TreeLogger.ERROR,
519: "Errors in "
520: + String.valueOf(result.getFileName()),
521: null);
522: IProblem[] errors = result.getErrors();
523: for (IProblem problem : errors) {
524: if (problemSet.contains(problem)) {
525: continue;
526: }
527:
528: problemSet.add(problem);
529:
530: // Strip the initial code from each error.
531: //
532: String msg = problem.toString();
533: msg = msg.substring(msg.indexOf(' '));
534:
535: // Append 'file (line): msg' to the error message.
536: //
537: int line = problem.getSourceLineNumber();
538: StringBuffer msgBuf = new StringBuffer();
539: msgBuf.append("Line ");
540: msgBuf.append(line);
541: msgBuf.append(": ");
542: msgBuf.append(msg);
543: branch.log(TreeLogger.ERROR, msgBuf.toString(),
544: null);
545: }
546: }
547: }
548: if (compilationFailed) {
549: logger.log(TreeLogger.ERROR,
550: "Cannot proceed due to previous errors", null);
551: throw new UnableToCompleteException();
552: }
553: }
554: }
|