KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > dev > GWTCompiler


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;
17
18 import com.google.gwt.core.ext.TreeLogger;
19 import com.google.gwt.core.ext.UnableToCompleteException;
20 import com.google.gwt.core.ext.TreeLogger.Type;
21 import com.google.gwt.core.ext.typeinfo.JClassType;
22 import com.google.gwt.core.ext.typeinfo.TypeOracle;
23 import com.google.gwt.dev.cfg.Compilation;
24 import com.google.gwt.dev.cfg.CompilationSchema;
25 import com.google.gwt.dev.cfg.Compilations;
26 import com.google.gwt.dev.cfg.ModuleDef;
27 import com.google.gwt.dev.cfg.ModuleDefLoader;
28 import com.google.gwt.dev.cfg.Properties;
29 import com.google.gwt.dev.cfg.Property;
30 import com.google.gwt.dev.cfg.PropertyPermutations;
31 import com.google.gwt.dev.cfg.Rules;
32 import com.google.gwt.dev.cfg.StaticPropertyOracle;
33 import com.google.gwt.dev.jdt.CacheManager;
34 import com.google.gwt.dev.jdt.RebindPermutationOracle;
35 import com.google.gwt.dev.jdt.StandardSourceOracle;
36 import com.google.gwt.dev.jdt.WebModeCompilerFrontEnd;
37 import com.google.gwt.dev.jjs.JavaToJavaScriptCompiler;
38 import com.google.gwt.dev.shell.StandardRebindOracle;
39 import com.google.gwt.dev.util.DefaultTextOutput;
40 import com.google.gwt.dev.util.SelectionScriptGenerator;
41 import com.google.gwt.dev.util.Util;
42 import com.google.gwt.dev.util.arg.ArgHandlerGenDir;
43 import com.google.gwt.dev.util.arg.ArgHandlerLogLevel;
44 import com.google.gwt.dev.util.arg.ArgHandlerScriptStyle;
45 import com.google.gwt.dev.util.arg.ArgHandlerTreeLoggerFlag;
46 import com.google.gwt.dev.util.log.AbstractTreeLogger;
47 import com.google.gwt.dev.util.log.DetachedTreeLoggerWindow;
48 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
49 import com.google.gwt.dev.util.xml.ReflectiveParser;
50 import com.google.gwt.util.tools.ArgHandlerExtra;
51 import com.google.gwt.util.tools.ArgHandlerOutDir;
52 import com.google.gwt.util.tools.ToolBase;
53 import com.google.gwt.util.tools.Utility;
54
55 import org.w3c.dom.Document JavaDoc;
56 import org.w3c.dom.Element JavaDoc;
57
58 import java.io.File JavaDoc;
59 import java.io.FileNotFoundException JavaDoc;
60 import java.io.FileReader JavaDoc;
61 import java.io.FilenameFilter JavaDoc;
62 import java.io.UnsupportedEncodingException JavaDoc;
63 import java.net.URL JavaDoc;
64 import java.util.ArrayList JavaDoc;
65 import java.util.HashMap JavaDoc;
66 import java.util.HashSet JavaDoc;
67 import java.util.Iterator JavaDoc;
68 import java.util.List JavaDoc;
69 import java.util.Map JavaDoc;
70 import java.util.Set JavaDoc;
71
72 import javax.xml.parsers.DocumentBuilder JavaDoc;
73 import javax.xml.parsers.DocumentBuilderFactory JavaDoc;
74 import javax.xml.parsers.ParserConfigurationException JavaDoc;
75
76 /**
77  * The main executable entry point for the GWT Java to JavaScript compiler.
78  */

79 public class GWTCompiler extends ToolBase {
80
81   private class ArgHandlerModuleName extends ArgHandlerExtra {
82
83     public boolean addExtraArg(String JavaDoc arg) {
84       setModuleName(arg);
85       return true;
86     }
87
88     public String JavaDoc getPurpose() {
89       return "Specifies the name of the module to compile";
90     }
91
92     public String JavaDoc[] getTagArgs() {
93       return new String JavaDoc[] {"module"};
94     }
95
96     public boolean isRequired() {
97       return true;
98     }
99   }
100
101   /**
102    * Used to smartly deal with rebind across the production of an entire
103    * permutation, including cache checking and recording the inputs and outputs
104    * into a {@link Compilation}.
105    */

106   private class CompilationRebindOracle extends StandardRebindOracle {
107
108     private final Map JavaDoc cache = new HashMap JavaDoc();
109
110     private Compilation compilation;
111
112     public CompilationRebindOracle() {
113       super(typeOracle, propOracle, rules, genDir, outDir, cacheManager);
114     }
115
116     /**
117      * Overridden so that we can selectively record inputs and outputs to derive
118      * the cache key for a compilation. Note that the cache gets invalidated if
119      * the propOracle changes state.
120      */

121     public String JavaDoc rebind(TreeLogger logger, String JavaDoc in)
122         throws UnableToCompleteException {
123       String JavaDoc out = (String JavaDoc) cache.get(in);
124       if (out == null) {
125         // Actually do the work, then cache it.
126
//
127
out = super.rebind(logger, in);
128         cache.put(in, out);
129       } else {
130         // Was cached.
131
//
132
String JavaDoc msg = "Rebind answer for '" + in + "' found in cache " + out;
133         logger.log(TreeLogger.DEBUG, msg, null);
134       }
135
136       if (compilation != null && compilation.recordDecision(in, out)) {
137         List JavaDoc genTypes = (List JavaDoc) generatedTypesByResultTypeName.get(out);
138         if (genTypes != null) {
139           for (Iterator JavaDoc iter = genTypes.iterator(); iter.hasNext();) {
140             JClassType genType = (JClassType) iter.next();
141             String JavaDoc sourceHash = genType.getTypeHash();
142             String JavaDoc genTypeName = genType.getQualifiedSourceName();
143             compilation.recordGeneratedTypeHash(genTypeName, sourceHash);
144           }
145         }
146       }
147
148       return out;
149     }
150
151     public void recordInto(Compilation compilation) {
152       this.compilation = compilation;
153     }
154   }
155
156   private class DistillerRebindPermutationOracle implements
157       RebindPermutationOracle {
158
159     private final StandardRebindOracle rebindOracle = new StandardRebindOracle(
160         typeOracle, propOracle, rules, genDir, outDir, cacheManager) {
161
162       /**
163        * Record generated types.
164        */

165       protected void onGeneratedTypes(String JavaDoc result, JClassType[] genTypes) {
166         List JavaDoc list = new ArrayList JavaDoc();
167         Util.addAll(list, genTypes);
168         Object JavaDoc existing = generatedTypesByResultTypeName.put(result, list);
169         assert (existing == null) : "Internal error: redundant notification of generated types";
170       }
171     };
172
173     public String JavaDoc[] getAllPossibleRebindAnswers(TreeLogger logger,
174         String JavaDoc requestTypeName) throws UnableToCompleteException {
175
176       String JavaDoc msg = "Computing all possible rebind results for '"
177           + requestTypeName + "'";
178       logger = logger.branch(TreeLogger.DEBUG, msg, null);
179
180       Set JavaDoc answers = new HashSet JavaDoc();
181
182       Property[] orderedProps = perms.getOrderedProperties();
183       for (Iterator JavaDoc iter = perms.iterator(); iter.hasNext();) {
184         String JavaDoc[] orderedPropValues = (String JavaDoc[]) iter.next();
185
186         // Create a snapshot of the property values by setting their values
187
// in the property oracle. Because my rebindOracle uses the shared
188
// generator context (which in turns uses the propOracle), this
189
// has the effect we're after. It isn't reentrant, though, so don't
190
// expect to call this recursively.
191
propOracle.setPropertyValues(orderedProps, orderedPropValues);
192
193         // Ask the rebind oracle.
194
logProperties(logger, orderedProps, orderedPropValues);
195         String JavaDoc resultTypeName = rebindOracle.rebind(logger, requestTypeName);
196         answers.add(resultTypeName);
197       }
198       return (String JavaDoc[]) Util.toArray(String JavaDoc.class, answers);
199     }
200   }
201
202   private static final String JavaDoc EXT_CACHE_XML = ".cache.xml";
203
204   public static void main(String JavaDoc[] args) {
205     /*
206      * NOTE: main always exits with a call to System.exit to terminate any
207      * non-daemon threads that were started in Generators. Typically, this is to
208      * shutdown AWT related threads, since the contract for their termination is
209      * still implementation-dependent.
210      */

211     GWTCompiler compiler = new GWTCompiler();
212     if (compiler.processArgs(args)) {
213       if (compiler.run()) {
214         // Exit w/ success code.
215
System.exit(0);
216       }
217     }
218     // Exit w/ non-success code.
219
System.exit(1);
220   }
221
222   private final CacheManager cacheManager;
223
224   private Compilations compilations = new Compilations();
225
226   private String JavaDoc[] declEntryPts;
227
228   private File JavaDoc genDir;
229
230   private Map JavaDoc generatedTypesByResultTypeName = new HashMap JavaDoc();
231
232   private JavaToJavaScriptCompiler jjs;
233
234   private Type logLevel;
235
236   private ModuleDef module;
237
238   private String JavaDoc moduleName;
239
240   private boolean obfuscate;
241
242   private File JavaDoc outDir;
243
244   private PropertyPermutations perms;
245
246   private boolean prettyNames;
247
248   private Properties properties;
249
250   private StaticPropertyOracle propOracle = new StaticPropertyOracle();
251
252   private RebindPermutationOracle rebindPermOracle;
253
254   private Rules rules;
255
256   private StandardSourceOracle sourceOracle;
257
258   private TypeOracle typeOracle;
259
260   private boolean useGuiLogger;
261
262   public GWTCompiler() {
263     this(null);
264   }
265
266   public GWTCompiler(CacheManager cacheManager) {
267     registerHandler(new ArgHandlerLogLevel() {
268       public void setLogLevel(Type level) {
269         logLevel = level;
270       }
271     });
272
273     registerHandler(new ArgHandlerGenDir() {
274       public void setDir(File JavaDoc dir) {
275         genDir = dir;
276       }
277     });
278
279     registerHandler(new ArgHandlerOutDir() {
280       public void setDir(File JavaDoc dir) {
281         outDir = dir;
282       }
283     });
284
285     registerHandler(new ArgHandlerTreeLoggerFlag() {
286       public boolean setFlag() {
287         useGuiLogger = true;
288         return true;
289       }
290     });
291
292     registerHandler(new ArgHandlerModuleName());
293
294     registerHandler(new ArgHandlerScriptStyle() {
295       public void setStyleDetailed() {
296         GWTCompiler.this.setStyleDetailed();
297       }
298
299       public void setStyleObfuscated() {
300         GWTCompiler.this.setStyleObfuscated();
301       }
302
303       public void setStylePretty() {
304         GWTCompiler.this.setStylePretty();
305       }
306     });
307     this.cacheManager = cacheManager;
308   }
309
310   public void distill(TreeLogger logger, ModuleDef moduleDef)
311       throws UnableToCompleteException {
312     this.module = moduleDef;
313
314     // Set up all the initial state.
315
checkModule(logger);
316
317     // Tweak the output directory so that output lives under the module name.
318
outDir = new File JavaDoc(outDir, module.getName());
319
320     rules = module.getRules();
321     typeOracle = module.getTypeOracle(logger);
322     sourceOracle = new StandardSourceOracle(typeOracle);
323     declEntryPts = module.getEntryPointTypeNames();
324     rebindPermOracle = new DistillerRebindPermutationOracle();
325     properties = module.getProperties();
326     perms = new PropertyPermutations(properties);
327     WebModeCompilerFrontEnd frontEnd = new WebModeCompilerFrontEnd(
328         sourceOracle, rebindPermOracle);
329     jjs = new JavaToJavaScriptCompiler(logger, frontEnd, declEntryPts,
330         obfuscate, prettyNames);
331     initCompilations(logger);
332
333     // Compile for every permutation of properties.
334
//
335
SelectionScriptGenerator selGen = compilePermutations(logger);
336
337     // Generate a selection script to pick the right permutation.
338
//
339
writeSelectionScripts(logger, selGen);
340
341     // Copy all public files into the output directory.
342
//
343
copyPublicFiles(logger);
344
345     logger.log(TreeLogger.INFO, "Compilation succeeded", null);
346   }
347
348   public File JavaDoc getGenDir() {
349     return genDir;
350   }
351
352   public Type getLogLevel() {
353     return logLevel;
354   }
355
356   public String JavaDoc getModuleName() {
357     return moduleName;
358   }
359
360   public boolean getUseGuiLogger() {
361     return useGuiLogger;
362   }
363
364   public void setGenDir(File JavaDoc dir) {
365     genDir = dir;
366   }
367
368   public void setLogLevel(Type level) {
369     this.logLevel = level;
370   }
371
372   public void setModuleName(String JavaDoc name) {
373     moduleName = name;
374   }
375
376   public void setOutDir(File JavaDoc outDir) {
377     this.outDir = outDir;
378   }
379
380   public void setStyleDetailed() {
381     obfuscate = false;
382     prettyNames = false;
383   }
384
385   public void setStyleObfuscated() {
386     obfuscate = true;
387   }
388
389   public void setStylePretty() {
390     obfuscate = false;
391     prettyNames = true;
392   }
393
394   private void checkModule(TreeLogger logger) throws UnableToCompleteException {
395     if (module.getEntryPointTypeNames().length == 0) {
396       logger.log(TreeLogger.ERROR, "Module has no entry points defined", null);
397       throw new UnableToCompleteException();
398     }
399   }
400
401   private SelectionScriptGenerator compilePermutations(TreeLogger logger)
402       throws UnableToCompleteException {
403     logger = logger.branch(TreeLogger.INFO, "Output will be written into "
404         + outDir, null);
405     Property[] orderedProps = perms.getOrderedProperties();
406     SelectionScriptGenerator selGen = new SelectionScriptGenerator(module,
407         orderedProps);
408     int permNumber = 1;
409     for (Iterator JavaDoc iter = perms.iterator(); iter.hasNext(); ++permNumber) {
410       String JavaDoc[] orderedPropValues = (String JavaDoc[]) iter.next();
411       String JavaDoc strongName = realizePermutation(logger, orderedProps,
412           orderedPropValues, permNumber);
413       selGen.recordSelection(orderedPropValues, strongName);
414     }
415     return selGen;
416   }
417
418   private void copyPublicFiles(TreeLogger logger)
419       throws UnableToCompleteException {
420     TreeLogger branch = null;
421     boolean anyCopied = false;
422     String JavaDoc[] files = module.getAllPublicFiles();
423     for (int i = 0; i < files.length; ++i) {
424       URL JavaDoc from = module.findPublicFile(files[i]);
425       File JavaDoc to = new File JavaDoc(outDir, files[i]);
426       boolean copied = Util.copy(logger, from, to);
427       if (copied) {
428         if (!anyCopied) {
429           branch = logger.branch(TreeLogger.INFO,
430               "Copying all files found on public path", null);
431           if (!logger.isLoggable(TreeLogger.TRACE)) {
432             branch = null;
433           }
434           anyCopied = true;
435         }
436
437         if (branch != null) {
438           branch.log(TreeLogger.TRACE, to.getAbsolutePath(), null);
439         }
440       }
441     }
442   }
443
444   private String JavaDoc getHtmlPrefix() {
445     DefaultTextOutput out = new DefaultTextOutput(obfuscate);
446     out.print("<html>");
447     out.newlineOpt();
448
449     // Setup the well-known variables.
450
//
451
out.print("<head><script>");
452     out.newlineOpt();
453     out.print("var $wnd = parent;");
454     out.newlineOpt();
455     out.print("var $doc = $wnd.document;");
456     out.newlineOpt();
457     out.print("var $moduleName, $moduleBase;");
458     out.newlineOpt();
459     out.print("</script></head>");
460     out.newlineOpt();
461     out.print("<body>");
462     out.newlineOpt();
463
464     // A nice message in case someone opens the file directly.
465
//
466
out.print("<font face='arial' size='-1'>This script is part of module</font>");
467     out.newlineOpt();
468     out.print("<code>");
469     out.print(module.getName());
470     out.print("</code>");
471     out.newlineOpt();
472
473     // Begin a script block inside the body. It's commented out so that the
474
// browser won't mistake strings containing "<script>" for actual script.
475
out.print("<script><!--");
476     out.newline();
477     return out.toString();
478   }
479
480   private String JavaDoc getHtmlSuffix() {
481     DefaultTextOutput out = new DefaultTextOutput(obfuscate);
482     String JavaDoc moduleFunction = module.getName().replace('.', '_');
483
484     // Generate the call to tell the bootstrap code that we're ready to go.
485
out.newlineOpt();
486     out.print("if ($wnd." + moduleFunction + ") $wnd." + moduleFunction
487         + ".onScriptLoad();");
488     out.newline();
489     out.print("--></script></body></html>");
490     out.newlineOpt();
491
492     return out.toString();
493   }
494
495   private String JavaDoc getJsPrefix() {
496     DefaultTextOutput out = new DefaultTextOutput(obfuscate);
497
498     out.print("(function(){");
499     out.newlineOpt();
500
501     // Setup the well-known variables.
502
//
503
out.print("var $wnd = window;");
504     out.newlineOpt();
505     out.print("var $doc = $wnd.document;");
506     out.newlineOpt();
507     out.print("var $moduleName, $moduleBase;");
508     out.newlineOpt();
509
510     return out.toString();
511   }
512
513   private String JavaDoc getJsSuffix() {
514     DefaultTextOutput out = new DefaultTextOutput(obfuscate);
515     String JavaDoc moduleFunction = module.getName().replace('.', '_');
516
517     // Generate the call to tell the bootstrap code that we're ready to go.
518
out.newlineOpt();
519     out.print("if (" + moduleFunction + ") {");
520     out.newlineOpt();
521     out.print(" var __gwt_initHandlers = " + moduleFunction
522         + ".__gwt_initHandlers;");
523     out.print(" " + moduleFunction + ".onScriptLoad(gwtOnLoad);");
524     out.newlineOpt();
525     out.print("}");
526     out.newlineOpt();
527     out.print("})();");
528     out.newlineOpt();
529
530     return out.toString();
531   }
532
533   /**
534    * This has to run after JJS exists which means that all rebind perms have
535    * happened and thus the type oracle knows about everything.
536    */

537   private void initCompilations(TreeLogger logger)
538       throws UnableToCompleteException {
539     File JavaDoc[] cacheXmls = outDir.listFiles(new FilenameFilter JavaDoc() {
540       public boolean accept(File JavaDoc dir, String JavaDoc name) {
541         return name.endsWith(EXT_CACHE_XML);
542       }
543     });
544
545     if (cacheXmls == null) {
546       return;
547     }
548
549     long newestCup = jjs.getLastModifiedTimeOfNewestCompilationUnit();
550     for (int i = 0; i < cacheXmls.length; i++) {
551       File JavaDoc cacheXml = cacheXmls[i];
552       String JavaDoc fn = cacheXml.getName();
553       String JavaDoc strongName = fn.substring(0, fn.length() - EXT_CACHE_XML.length());
554
555       // Make sure the cached script is not out of date.
556
//
557
long cacheXmlLastMod = cacheXml.lastModified();
558       if (cacheXmlLastMod < newestCup) {
559         // It is out of date; no need to even parse the XML.
560
//
561
String JavaDoc msg = "Compilation '" + fn
562             + "' is out of date and will be removed";
563         logger.log(TreeLogger.TRACE, msg, null);
564         Util.deleteFilesStartingWith(outDir, strongName);
565         continue;
566       }
567
568       // It is up-to-date, so we at least can load it.
569
// We still need to verify that the source for generated types hasn't
570
// changed.
571
//
572
TreeLogger branch = logger.branch(TreeLogger.DEBUG,
573           "Loading cached compilation: " + cacheXml, null);
574       Compilation c = new Compilation();
575       c.setStrongName(strongName);
576       CompilationSchema schema = new CompilationSchema(c);
577       FileReader JavaDoc r = null;
578       Throwable JavaDoc caught = null;
579       try {
580         r = new FileReader JavaDoc(cacheXml);
581         ReflectiveParser.parse(logger, schema, r);
582       } catch (FileNotFoundException JavaDoc e) {
583         caught = e;
584       } catch (UnableToCompleteException e) {
585         caught = e;
586       } finally {
587         Utility.close(r);
588       }
589
590       if (caught != null) {
591         String JavaDoc msg = "Unable to load the cached file";
592         branch.log(TreeLogger.WARN, msg, caught);
593         continue;
594       }
595
596       // Check that the hash code of the generated sources for this compilation
597
// matches the current generated source for the same type.
598
//
599
boolean isBadCompilation = false;
600       String JavaDoc[] genTypes = c.getGeneratedTypeNames();
601       for (int j = 0; j < genTypes.length; j++) {
602         String JavaDoc genTypeName = genTypes[j];
603         String JavaDoc cachedHash = c.getTypeHash(genTypeName);
604         JClassType genType = typeOracle.findType(genTypeName);
605         if (genType == null) {
606           // This cache entry refers to a type that no longer seems to exist.
607
// Remove it.
608
//
609
String JavaDoc msg = "Compilation '" + fn + "' refers to generated type '"
610               + genTypeName
611               + "' which no longer exists; cache entry will be removed";
612           branch.log(TreeLogger.TRACE, msg, null);
613           Util.deleteFilesStartingWith(outDir, strongName);
614           isBadCompilation = true;
615           break;
616         }
617
618         String JavaDoc currentHash = genType.getTypeHash();
619
620         if (!cachedHash.equals(currentHash)) {
621           String JavaDoc msg = "Compilation '"
622               + fn
623               + "' was compiled with a different version of generated source for '"
624               + genTypeName + "'; cache entry will be removed";
625           branch.log(TreeLogger.TRACE, msg, null);
626           Util.deleteFilesStartingWith(outDir, strongName);
627           isBadCompilation = true;
628           break;
629         }
630       }
631
632       if (!isBadCompilation) {
633         // Okay -- this compilation should be a cache candidate.
634
compilations.add(c);
635       }
636     }
637   }
638
639   private void logProperties(TreeLogger logger, Property[] props,
640       String JavaDoc[] values) {
641     if (logger.isLoggable(TreeLogger.DEBUG)) {
642       logger = logger.branch(TreeLogger.DEBUG, "Setting properties", null);
643       for (int i = 0; i < props.length; i++) {
644         String JavaDoc name = props[i].getName();
645         String JavaDoc value = values[i];
646         logger.log(TreeLogger.TRACE, name + " = " + value, null);
647       }
648     }
649   }
650
651   /**
652    * Attempts to compile with a single permutation of properties. The result can
653    * be one of the following:
654    * <ul>
655    * <li>There is an existing compilation having the same deferred binding
656    * results (and thus would create identical output); compilation is skipped
657    * <li>No existing compilation unit matches, so the compilation proceeds
658    * </ul>
659    */

660   private String JavaDoc realizePermutation(TreeLogger logger, Property[] currentProps,
661       String JavaDoc[] currentValues, int permNumber) throws UnableToCompleteException {
662     String JavaDoc msg = "Analyzing permutation #" + permNumber;
663     logger = logger.branch(TreeLogger.TRACE, msg, null);
664
665     logProperties(logger, currentProps, currentValues);
666
667     // Create a rebind oracle that will record decisions so that we can cache
668
// them and avoid future computations.
669
//
670
CompilationRebindOracle rebindOracle = new CompilationRebindOracle();
671
672     // Tell the property provider above about the current property values.
673
// Note that the rebindOracle is actually sensitive to these values because
674
// in its ctor is uses propOracle as its property oracle.
675
//
676
propOracle.setPropertyValues(currentProps, currentValues);
677
678     // Check to see if we already have this compilation.
679
// This will have the effect of filling the rebind oracle's cache.
680
//
681
String JavaDoc[] entryPts = module.getEntryPointTypeNames();
682     Compilation cached = compilations.find(logger, rebindOracle, entryPts);
683     if (cached != null) {
684       msg = "Matches existing compilation " + cached.getStrongName();
685       logger.log(TreeLogger.TRACE, msg, null);
686       return cached.getStrongName();
687     }
688
689     // Now attach a compilation into which we can record the particular inputs
690
// and outputs used by this compile process.
691
//
692
Compilation compilation = new Compilation();
693     rebindOracle.recordInto(compilation);
694
695     // Create JavaScript.
696
//
697
String JavaDoc js = jjs.compile(logger, rebindOracle);
698
699     // Create a wrapper and an unambiguous name for the file.
700
//
701
String JavaDoc strongName = writeHtmlAndJsWithStrongName(logger, js);
702
703     // Write out a cache control file that correlates to this script.
704
//
705
compilation.setStrongName(strongName);
706     writeCacheFile(logger, compilation);
707
708     // Add this compilation to the list of known compilations.
709
//
710
compilations.add(compilation);
711     return compilation.getStrongName();
712   }
713
714   /**
715    * Runs the compiler. If a gui-based TreeLogger is used, this method will not
716    * return until its window is closed by the user.
717    *
718    * @return success from the compiler, <code>true</code> if the compile
719    * completed without errors, <code>false</code> otherwise.
720    */

721   private boolean run() {
722     // Set any platform specific system properties.
723
BootStrapPlatform.setSystemProperties();
724
725     if (useGuiLogger) {
726       // Initialize a tree logger window.
727
DetachedTreeLoggerWindow loggerWindow = DetachedTreeLoggerWindow.getInstance(
728           "Build Output for " + moduleName, 800, 600, true);
729
730       // Eager AWT initialization for OS X to ensure safe coexistence with SWT.
731
BootStrapPlatform.maybeInitializeAWT();
732
733       final AbstractTreeLogger logger = loggerWindow.getLogger();
734       final boolean[] success = new boolean[1];
735
736       // Compiler will be spawned onto a second thread, UI thread for tree
737
// logger will remain on the main.
738
Thread JavaDoc compilerThread = new Thread JavaDoc(new Runnable JavaDoc() {
739         public void run() {
740           success[0] = GWTCompiler.this.run(logger);
741         }
742       });
743
744       compilerThread.setName("GWTCompiler Thread");
745       compilerThread.start();
746       loggerWindow.run();
747
748       // Even if the tree logger window is closed, we wait for the compiler
749
// to finish.
750
waitForThreadToTerminate(compilerThread);
751
752       return success[0];
753     } else {
754       return run(new PrintWriterTreeLogger());
755     }
756   }
757
758   private boolean run(AbstractTreeLogger logger) {
759     try {
760       logger.setMaxDetail(logLevel);
761
762       ModuleDef moduleDef = ModuleDefLoader.loadFromClassPath(logger,
763           moduleName);
764       distill(logger, moduleDef);
765       return true;
766     } catch (UnableToCompleteException e) {
767       // We intentionally don't pass in the exception here since the real
768
// cause has been logged.
769
logger.log(TreeLogger.ERROR, "Build failed", null);
770       return false;
771     }
772   }
773
774   /**
775    * Waits for a thread to terminate before it returns. This method is a
776    * non-cancellable task, in that it will defer thread interruption until it is
777    * done.
778    *
779    * @param godot the thread that is being waited on.
780    */

781   private void waitForThreadToTerminate(final Thread JavaDoc godot) {
782     // Goetz pattern for non-cancellable tasks.
783
// http://www-128.ibm.com/developerworks/java/library/j-jtp05236.html
784
boolean isInterrupted = false;
785     try {
786       while (true) {
787         try {
788           godot.join();
789           return;
790         } catch (InterruptedException JavaDoc e) {
791           isInterrupted = true;
792         }
793       }
794     } finally {
795       if (isInterrupted) {
796         Thread.currentThread().interrupt();
797       }
798     }
799   }
800
801   private void writeCacheFile(TreeLogger logger, Compilation compilation)
802       throws UnableToCompleteException {
803     // Create and write the cache file.
804
// The format matches the one read in consumeCacheEntry().
805
//
806
DocumentBuilderFactory JavaDoc dbf = DocumentBuilderFactory.newInstance();
807     DocumentBuilder JavaDoc db;
808     try {
809       db = dbf.newDocumentBuilder();
810     } catch (ParserConfigurationException JavaDoc e) {
811       logger.log(TreeLogger.ERROR, "Unable to construct cache entry XML", e);
812       throw new UnableToCompleteException();
813     }
814     Document JavaDoc doc = db.newDocument();
815
816     // <cache-entry ...>
817
Element JavaDoc docElem = doc.createElement("cache-entry");
818     doc.appendChild(docElem);
819
820     // <generated-type-hash ...>
821
String JavaDoc[] genTypeNames = compilation.getGeneratedTypeNames();
822     for (int i = 0; i < genTypeNames.length; i++) {
823       String JavaDoc genTypeName = genTypeNames[i];
824       String JavaDoc hash = compilation.getTypeHash(genTypeName);
825       Element JavaDoc childElem = doc.createElement("generated-type-hash");
826       docElem.appendChild(childElem);
827       childElem.setAttribute("class", genTypeName);
828       childElem.setAttribute("hash", hash);
829     }
830
831     // deferred binding decisions
832
String JavaDoc[] inputs = compilation.getRebindInputs();
833     for (int i = 0, n = inputs.length; i < n; ++i) {
834       String JavaDoc in = inputs[i];
835       String JavaDoc out = compilation.getRebindOutput(in);
836
837       // <rebind-decision ...>
838
Element JavaDoc childElem = doc.createElement("rebind-decision");
839       docElem.appendChild(childElem);
840       childElem.setAttribute("in", in);
841       childElem.setAttribute("out", out);
842     }
843
844     // Persist it.
845
//
846
String JavaDoc strongName = compilation.getStrongName();
847     File JavaDoc cacheFile = new File JavaDoc(outDir, strongName + EXT_CACHE_XML);
848     byte[] bytes = Util.toXmlUtf8(doc);
849     Util.writeBytesToFile(logger, cacheFile, bytes);
850
851     String JavaDoc msg = "Compilation metadata written to "
852         + cacheFile.getAbsolutePath();
853     logger.log(TreeLogger.TRACE, msg, null);
854   }
855
856   /**
857    * Writes the script to 1) an HTML file containing the script and 2) a
858    * JavaScript file containing the script. Contenst are encoded as UTF-8, and
859    * the filenames will be an MD5 hash of the script contents.
860    *
861    * @return the base part of the strong name, which can be used to create other
862    * files with different extensions
863    */

864   private String JavaDoc writeHtmlAndJsWithStrongName(TreeLogger logger, String JavaDoc js)
865       throws UnableToCompleteException {
866     try {
867       byte[] scriptBytes = js.getBytes("UTF-8");
868       String JavaDoc strongName = Util.computeStrongName(scriptBytes);
869       {
870         byte[] prefix = getHtmlPrefix().getBytes("UTF-8");
871         byte[] suffix = getHtmlSuffix().getBytes("UTF-8");
872         File JavaDoc outFile = new File JavaDoc(outDir, strongName + ".cache.html");
873         Util.writeBytesToFile(logger, outFile, new byte[][] {
874             prefix, scriptBytes, suffix});
875         String JavaDoc msg = "Compilation written to " + outFile.getAbsolutePath();
876         logger.log(TreeLogger.TRACE, msg, null);
877       }
878       {
879         byte[] prefix = getJsPrefix().getBytes("UTF-8");
880         byte[] suffix = getJsSuffix().getBytes("UTF-8");
881         File JavaDoc outFile = new File JavaDoc(outDir, strongName + ".cache.js");
882         Util.writeBytesToFile(logger, outFile, new byte[][] {
883             prefix, scriptBytes, suffix});
884         String JavaDoc msg = "Compilation written to " + outFile.getAbsolutePath();
885         logger.log(TreeLogger.TRACE, msg, null);
886       }
887       return strongName;
888     } catch (UnsupportedEncodingException JavaDoc e) {
889       logger.log(TreeLogger.ERROR, "Unable to encode compiled script as UTF-8",
890           e);
891       throw new UnableToCompleteException();
892     }
893   }
894
895   private void writeSelectionScripts(TreeLogger logger,
896       SelectionScriptGenerator selGen) {
897     {
898       String JavaDoc html = selGen.generateSelectionScript(obfuscate, false);
899       String JavaDoc fn = module.getName() + ".nocache.js";
900       File JavaDoc selectionFile = new File JavaDoc(outDir, fn);
901       Util.writeStringAsFile(selectionFile, html);
902       String JavaDoc msg = "Compilation selection script written to "
903           + selectionFile.getAbsolutePath();
904       logger.log(TreeLogger.TRACE, msg, null);
905     }
906     {
907       String JavaDoc html = selGen.generateSelectionScript(obfuscate, true);
908       String JavaDoc fn = module.getName() + "-xs.nocache.js";
909       File JavaDoc selectionFile = new File JavaDoc(outDir, fn);
910       Util.writeStringAsFile(selectionFile, html);
911       String JavaDoc msg = "Compilation selection script written to "
912           + selectionFile.getAbsolutePath();
913       logger.log(TreeLogger.TRACE, msg, null);
914     }
915   }
916 }
917
Popular Tags