KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > google > gwt > dev > shell > StandardGeneratorContext


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.shell;
17
18 import com.google.gwt.core.ext.GeneratorContext;
19 import com.google.gwt.core.ext.PropertyOracle;
20 import com.google.gwt.core.ext.TreeLogger;
21 import com.google.gwt.core.ext.UnableToCompleteException;
22 import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
23 import com.google.gwt.core.ext.typeinfo.JClassType;
24 import com.google.gwt.core.ext.typeinfo.NotFoundException;
25 import com.google.gwt.core.ext.typeinfo.TypeOracle;
26 import com.google.gwt.dev.jdt.CacheManager;
27 import com.google.gwt.dev.jdt.StaticCompilationUnitProvider;
28 import com.google.gwt.dev.jdt.TypeOracleBuilder;
29 import com.google.gwt.dev.jdt.URLCompilationUnitProvider;
30 import com.google.gwt.dev.util.Util;
31
32 import java.io.ByteArrayOutputStream JavaDoc;
33 import java.io.CharArrayWriter JavaDoc;
34 import java.io.File JavaDoc;
35 import java.io.IOException JavaDoc;
36 import java.io.OutputStream JavaDoc;
37 import java.io.PrintWriter JavaDoc;
38 import java.net.MalformedURLException JavaDoc;
39 import java.net.URL JavaDoc;
40 import java.util.ArrayList JavaDoc;
41 import java.util.HashSet JavaDoc;
42 import java.util.IdentityHashMap JavaDoc;
43 import java.util.Iterator JavaDoc;
44 import java.util.List JavaDoc;
45 import java.util.Map JavaDoc;
46 import java.util.Set JavaDoc;
47
48 /**
49  * An abstract implementation of a generator context in terms of a
50  * {@link com.google.gwt.dev.jdt.MutableCompilationServiceHost}, a
51  * {@link com.google.gwt.dev.jdt.PropertyOracle}, and a
52  * {@link com.google.gwt.core.server.typeinfo.TypeOracle}. The generator
53  * interacts with the mutable source oracle by increasing the available
54  * compilation units as they are generated.
55  */

56 public class StandardGeneratorContext implements GeneratorContext {
57
58   /**
59    * This compilation unit provider acts as a normal compilation unit provider
60    * as well as a buffer into which generators can write their source. A
61    * controller should ensure that source isn't requested until the generator
62    * has finished writing it.
63    */

64   private static class GeneratedCompilationUnitProvider extends
65       StaticCompilationUnitProvider {
66
67     public CharArrayWriter JavaDoc caw;
68
69     public PrintWriter JavaDoc pw;
70
71     public char[] source;
72
73     public GeneratedCompilationUnitProvider(String JavaDoc packageName,
74         String JavaDoc simpleTypeName) {
75       super(packageName, simpleTypeName, null);
76       caw = new CharArrayWriter JavaDoc();
77       pw = new PrintWriter JavaDoc(caw, true);
78     }
79
80     /**
81      * Finalizes the source and adds this compilation unit to the host.
82      */

83     public void commit() {
84       source = caw.toCharArray();
85       pw.close();
86       pw = null;
87       caw.close();
88       caw = null;
89     }
90
91     public char[] getSource() {
92       if (source == null) {
93         throw new IllegalStateException JavaDoc("source not committed");
94       }
95       return source;
96     }
97   }
98
99   /**
100    * {@link CompilationUnitProvider} used to represent generated source code
101    * which is stored on disk. This class is only used if the -gen flag is
102    * specified.
103    */

104   private static final class GeneratedCUP extends URLCompilationUnitProvider {
105     private GeneratedCUP(URL JavaDoc url, String JavaDoc name) {
106       super(url, name);
107     }
108
109     public long getLastModified() throws UnableToCompleteException {
110       // Make it seem really old so it won't cause recompiles.
111
//
112
return 0L;
113     }
114     
115     public boolean isTransient() {
116       return true;
117     }
118   }
119
120   /**
121    * Manages a resource that is in the process of being created by a generator.
122    */

123   private static class PendingResource {
124
125     private final File JavaDoc pendingFile;
126     private final ByteArrayOutputStream JavaDoc baos = new ByteArrayOutputStream JavaDoc();
127
128     public PendingResource(File JavaDoc pendingFile) {
129       this.pendingFile = pendingFile;
130     }
131
132     public void commit(TreeLogger logger) throws UnableToCompleteException {
133       logger = logger.branch(TreeLogger.TRACE, "Writing generated resource '"
134           + pendingFile.getAbsolutePath() + "'", null);
135
136       if (pendingFile.exists()) {
137         logger.log(TreeLogger.ERROR,
138             "The destination file already exists; aborting the commit", null);
139         throw new UnableToCompleteException();
140       }
141
142       Util.writeBytesToFile(logger, pendingFile, baos.toByteArray());
143     }
144
145     public File JavaDoc getFile() {
146       return pendingFile;
147     }
148
149     public OutputStream JavaDoc getOutputStream() {
150       return baos;
151     }
152
153     public boolean isSamePath(TreeLogger logger, File JavaDoc other) {
154       File JavaDoc failedFile = null;
155       try {
156         /*
157          * We try to convert both files to their canonical form. Either one
158          * might throw an exception, so we keep track of the one being converted
159          * so that we can say which one failed in the case of an error.
160          */

161         failedFile = pendingFile;
162         File JavaDoc thisFile = pendingFile.getCanonicalFile();
163         failedFile = pendingFile;
164         File JavaDoc thatFile = other.getCanonicalFile();
165
166         if (thisFile.equals(thatFile)) {
167           return true;
168         } else {
169           return false;
170         }
171       } catch (IOException JavaDoc e) {
172         logger.log(TreeLogger.ERROR,
173             "Unable to determine canonical path of pending resource '"
174                 + failedFile.toString() + "'", e);
175       }
176       return false;
177     }
178   }
179
180   private final CacheManager cacheManager;
181
182   private final Set JavaDoc committedGeneratedCups = new HashSet JavaDoc();
183
184   private final File JavaDoc genDir;
185
186   private final Set JavaDoc generatedTypeNames = new HashSet JavaDoc();
187
188   private final File JavaDoc outDir;
189
190   private final Map JavaDoc pendingResourcesByOutputStream = new IdentityHashMap JavaDoc();
191
192   private final PropertyOracle propOracle;
193
194   private final TypeOracle typeOracle;
195
196   private final Map JavaDoc uncommittedGeneratedCupsByPrintWriter = new IdentityHashMap JavaDoc();
197
198   /**
199    * Normally, the compiler host would be aware of the same types that are
200    * available in the supplied type oracle although it isn't strictly required.
201    */

202   public StandardGeneratorContext(TypeOracle typeOracle,
203       PropertyOracle propOracle, File JavaDoc genDir, File JavaDoc outDir,
204       CacheManager cacheManager) {
205     this.propOracle = propOracle;
206     this.typeOracle = typeOracle;
207     this.genDir = genDir;
208     this.outDir = outDir;
209     this.cacheManager = cacheManager;
210   }
211
212   /**
213    * Commits a pending generated type.
214    */

215   public final void commit(TreeLogger logger, PrintWriter JavaDoc pw) {
216     GeneratedCompilationUnitProvider gcup = (GeneratedCompilationUnitProvider) uncommittedGeneratedCupsByPrintWriter.get(pw);
217     if (gcup != null) {
218       gcup.commit();
219       uncommittedGeneratedCupsByPrintWriter.remove(pw);
220       committedGeneratedCups.add(gcup);
221     } else {
222       logger.log(TreeLogger.WARN,
223           "Generator attempted to commit an unknown PrintWriter", null);
224     }
225   }
226
227   public void commitResource(TreeLogger logger, OutputStream JavaDoc os)
228       throws UnableToCompleteException {
229
230     // Find the pending resource using its output stream as a key.
231
PendingResource pendingResource = (PendingResource) pendingResourcesByOutputStream.get(os);
232     if (pendingResource != null) {
233       // Actually write the bytes to disk.
234
pendingResource.commit(logger);
235
236       // The resource is now no longer pending, so remove it from the map.
237
// If the commit above throws an exception, it's okay to leave the entry
238
// in the map because it will be reported later as not having been
239
// committed, which is accurate.
240
pendingResourcesByOutputStream.remove(os);
241     } else {
242       logger.log(TreeLogger.WARN,
243           "Generator attempted to commit an unknown OutputStream", null);
244       throw new UnableToCompleteException();
245     }
246   }
247
248   /**
249    * Call this whenever generators are known to not be running to clear out
250    * uncommitted compilation units and to force committed compilation units to
251    * be parsed and added to the type oracle.
252    *
253    * @return types generated during this object's lifetime
254    */

255   public final JClassType[] finish(TreeLogger logger)
256       throws UnableToCompleteException {
257
258     abortUncommittedResources(logger);
259
260     // Process pending generated types.
261
List JavaDoc genTypeNames = new ArrayList JavaDoc();
262
263     try {
264       TreeLogger branch;
265       if (!committedGeneratedCups.isEmpty()) {
266         // Assimilate the new types into the type oracle.
267
//
268
String JavaDoc msg = "Assimilating generated source";
269         branch = logger.branch(TreeLogger.DEBUG, msg, null);
270
271         TreeLogger subBranch = null;
272         if (branch.isLoggable(TreeLogger.DEBUG)) {
273           subBranch = branch.branch(TreeLogger.DEBUG,
274               "Generated source files...", null);
275         }
276
277         assert (cacheManager.getTypeOracle() == typeOracle);
278         TypeOracleBuilder builder = new TypeOracleBuilder(cacheManager);
279         for (Iterator JavaDoc iter = committedGeneratedCups.iterator(); iter.hasNext();) {
280           GeneratedCompilationUnitProvider gcup = (GeneratedCompilationUnitProvider) iter.next();
281           String JavaDoc typeName = gcup.getTypeName();
282           String JavaDoc genTypeName = gcup.getPackageName() + "." + typeName;
283           genTypeNames.add(genTypeName);
284           CompilationUnitProvider cup = writeSource(logger, gcup, typeName);
285           builder.addCompilationUnit(cup);
286           cacheManager.addGeneratedCup(cup);
287           
288           if (subBranch != null) {
289             subBranch.log(TreeLogger.DEBUG, cup.getLocation(), null);
290           }
291         }
292         
293         builder.build(branch);
294       }
295
296       // Return the generated types.
297
JClassType[] genTypes = new JClassType[genTypeNames.size()];
298       int next = 0;
299       for (Iterator JavaDoc iter = genTypeNames.iterator(); iter.hasNext();) {
300         String JavaDoc genTypeName = (String JavaDoc) iter.next();
301         try {
302           genTypes[next++] = typeOracle.getType(genTypeName);
303         } catch (NotFoundException e) {
304           String JavaDoc msg = "Unable to find recently-generated type '" + genTypeName;
305           logger.log(TreeLogger.ERROR, msg, null);
306           throw new UnableToCompleteException();
307         }
308       }
309       return genTypes;
310     } finally {
311
312       // Remind the user if there uncommitted cups.
313
if (!uncommittedGeneratedCupsByPrintWriter.isEmpty()) {
314         String JavaDoc msg = "For the following type(s), generated source was never committed (did you forget to call commit()?)";
315         logger = logger.branch(TreeLogger.WARN, msg, null);
316
317         for (Iterator JavaDoc iter = uncommittedGeneratedCupsByPrintWriter.values().iterator(); iter.hasNext();) {
318           StaticCompilationUnitProvider cup = (StaticCompilationUnitProvider) iter.next();
319           String JavaDoc typeName = cup.getPackageName() + "." + cup.getTypeName();
320           logger.log(TreeLogger.WARN, typeName, null);
321         }
322       }
323
324       uncommittedGeneratedCupsByPrintWriter.clear();
325       committedGeneratedCups.clear();
326       generatedTypeNames.clear();
327     }
328   }
329
330   public File JavaDoc getOutputDir() {
331     return outDir;
332   }
333
334   public final PropertyOracle getPropertyOracle() {
335     return propOracle;
336   }
337
338   public final TypeOracle getTypeOracle() {
339     return typeOracle;
340   }
341
342   public final PrintWriter JavaDoc tryCreate(TreeLogger logger, String JavaDoc packageName,
343       String JavaDoc simpleTypeName) {
344     String JavaDoc typeName = packageName + "." + simpleTypeName;
345
346     // Is type already known to the host?
347
JClassType existingType = typeOracle.findType(packageName, simpleTypeName);
348     if (existingType != null) {
349       logger.log(TreeLogger.DEBUG, "Type '" + typeName
350           + "' already exists and will not be re-created ", null);
351       return null;
352     }
353
354     // Has anybody tried to create this type during this iteration?
355
if (generatedTypeNames.contains(typeName)) {
356       final String JavaDoc msg = "A request to create type '"
357           + typeName
358           + "' was received while the type itself was being created; this might be a generator or configuration bug";
359       logger.log(TreeLogger.WARN, msg, null);
360       return null;
361     }
362
363     // The type isn't there, so we can let the caller create it. Remember that
364
// it is pending so another attempt to create the same type will fail.
365
GeneratedCompilationUnitProvider gcup = new GeneratedCompilationUnitProvider(
366         packageName, simpleTypeName);
367     uncommittedGeneratedCupsByPrintWriter.put(gcup.pw, gcup);
368     generatedTypeNames.add(typeName);
369
370     return gcup.pw;
371   }
372
373   public OutputStream JavaDoc tryCreateResource(TreeLogger logger, String JavaDoc name)
374       throws UnableToCompleteException {
375
376     logger = logger.branch(TreeLogger.DEBUG,
377         "Preparing pending output resource '" + name + "'", null);
378
379     // Disallow null or empty names.
380
if (name == null || name.trim().equals("")) {
381       logger.log(TreeLogger.ERROR,
382           "The resource name must be a non-empty string", null);
383       throw new UnableToCompleteException();
384     }
385
386     // Disallow absolute paths.
387
File JavaDoc f = new File JavaDoc(name);
388     if (f.isAbsolute()) {
389       logger.log(
390           TreeLogger.ERROR,
391           "Resource paths are intended to be relative to the compiled output directory and cannot be absolute",
392           null);
393       throw new UnableToCompleteException();
394     }
395
396     // Disallow backslashes (to promote consistency in calling code).
397
if (name.indexOf('\\') >= 0) {
398       logger.log(
399           TreeLogger.ERROR,
400           "Resource paths must contain forward slashes (not backslashes) to denote subdirectories",
401           null);
402       throw new UnableToCompleteException();
403     }
404
405     // Compute the final path.
406
File JavaDoc pendingFile = new File JavaDoc(outDir, name);
407
408     // See if this file is already pending.
409
for (Iterator JavaDoc iter = pendingResourcesByOutputStream.values().iterator(); iter.hasNext();) {
410       PendingResource pendingResource = (PendingResource) iter.next();
411       if (pendingResource.isSamePath(logger, pendingFile)) {
412         // It is already pending.
413
logger.log(TreeLogger.WARN, "The file is already a pending resource",
414             null);
415         return null;
416       }
417     }
418
419     // If this file already exists, we won't overwrite it.
420
if (pendingFile.exists()) {
421       logger.log(TreeLogger.TRACE, "File already exists", null);
422       return null;
423     }
424
425     // Record that this file is pending.
426
PendingResource pendingResource = new PendingResource(pendingFile);
427     OutputStream JavaDoc os = pendingResource.getOutputStream();
428     pendingResourcesByOutputStream.put(os, pendingResource);
429
430     return os;
431   }
432
433   private void abortUncommittedResources(TreeLogger logger) {
434     if (pendingResourcesByOutputStream.isEmpty()) {
435       // Nothing to do.
436
return;
437     }
438
439     // Warn the user about uncommitted resources.
440
logger = logger.branch(
441         TreeLogger.WARN,
442         "The following resources will not be created because they were never committed (did you forget to call commit()?)",
443         null);
444
445     try {
446       for (Iterator JavaDoc iter = pendingResourcesByOutputStream.values().iterator(); iter.hasNext();) {
447         PendingResource pendingResource = (PendingResource) iter.next();
448         logger.log(TreeLogger.WARN,
449             pendingResource.getFile().getAbsolutePath(), null);
450       }
451     } finally {
452       pendingResourcesByOutputStream.clear();
453     }
454   }
455
456   /**
457    * Writes the source of the specified compilation unit to disk if a gen
458    * directory is specified.
459    *
460    * @param cup the compilation unit whose contents might need to be written
461    * @param simpleTypeName the fully-qualified type name
462    * @return a wrapper for the existing cup with a proper location
463    */

464   private CompilationUnitProvider writeSource(TreeLogger logger,
465       CompilationUnitProvider cup, String JavaDoc simpleTypeName)
466       throws UnableToCompleteException {
467
468     if (genDir == null) {
469       // No place to write it.
470
return cup;
471     }
472
473     if (Util.isCompilationUnitOnDisk(cup.getLocation())) {
474       // Already on disk.
475
return cup;
476     }
477
478     // Let's do write it.
479
String JavaDoc typeName = cup.getPackageName() + "." + simpleTypeName;
480     String JavaDoc relativePath = typeName.replace('.', '/') + ".java";
481     File JavaDoc srcFile = new File JavaDoc(genDir, relativePath);
482     Util.writeCharsAsFile(logger, srcFile, cup.getSource());
483
484     // Update the location of the cup
485
Throwable JavaDoc caught = null;
486     try {
487       URL JavaDoc fileURL = srcFile.toURL();
488       URLCompilationUnitProvider fileBaseCup = new GeneratedCUP(fileURL,
489           cup.getPackageName());
490       return fileBaseCup;
491     } catch (MalformedURLException JavaDoc e) {
492       caught = e;
493     }
494     logger.log(TreeLogger.ERROR,
495         "Internal error: cannot build URL from synthesized file name '"
496             + srcFile.getAbsolutePath() + "'", caught);
497     throw new UnableToCompleteException();
498   }
499 }
Popular Tags