View Javadoc

1   /*
2    $Id: CompilationUnit.java 4661 2007-01-02 16:52:26Z blackdrag $
3   
4   
5    Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
6   
7   
8    Redistribution and use of this software and associated documentation
9    ("Software"), with or without modification, are permitted provided
10   that the following conditions are met:
11  
12   1. Redistributions of source code must retain copyright
13      statements and notices.  Redistributions must also contain a
14      copy of this document.
15  
16  
17   2. Redistributions in binary form must reproduce the
18      above copyright notice, this list of conditions and the
19      following disclaimer in the documentation and/or other
20      materials provided with the distribution.
21  
22  
23   3. The name "groovy" must not be used to endorse or promote
24      products derived from this Software without prior written
25      permission of The Codehaus.  For written permission,
26      please contact info@codehaus.org.
27  
28  
29   4. Products derived from this Software may not be called "groovy"
30      nor may "groovy" appear in their names without prior written
31      permission of The Codehaus. "groovy" is a registered
32      trademark of The Codehaus.
33  
34  
35   5. Due credit should be given to The Codehaus -
36      http://groovy.codehaus.org/
37  
38  
39   THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
40   ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
41   NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
42   FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
43   THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
44   INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
45   (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
46   SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
47   HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
48   STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
49   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
50   OF THE POSSIBILITY OF SUCH DAMAGE.
51   */
52  
53  
54  package org.codehaus.groovy.control;
55  
56  import java.io.File;
57  import java.io.FileOutputStream;
58  import java.io.IOException;
59  import java.io.InputStream;
60  import java.net.URL;
61  import java.security.CodeSource;
62  import java.util.*;
63  
64  import org.codehaus.groovy.GroovyBugError;
65  import org.codehaus.groovy.ast.ASTNode;
66  import org.codehaus.groovy.ast.ClassNode;
67  import org.codehaus.groovy.ast.CompileUnit;
68  import org.codehaus.groovy.ast.ModuleNode;
69  import org.codehaus.groovy.classgen.AsmClassGenerator;
70  import org.codehaus.groovy.classgen.ClassCompletionVerifier;
71  import org.codehaus.groovy.classgen.ClassGenerator;
72  import org.codehaus.groovy.classgen.GeneratorContext;
73  import org.codehaus.groovy.classgen.VariableScopeVisitor;
74  import org.codehaus.groovy.classgen.Verifier;
75  import org.codehaus.groovy.control.io.InputStreamReaderSource;
76  import org.codehaus.groovy.control.io.ReaderSource;
77  import org.codehaus.groovy.control.messages.ExceptionMessage;
78  import org.codehaus.groovy.control.messages.Message;
79  import org.codehaus.groovy.control.messages.SimpleMessage;
80  import org.codehaus.groovy.syntax.SyntaxException;
81  import org.codehaus.groovy.syntax.ClassSource;
82  import org.codehaus.groovy.syntax.SourceSummary;
83  import org.codehaus.groovy.tools.GroovyClass;
84  import org.objectweb.asm.ClassVisitor;
85  import org.objectweb.asm.ClassWriter;
86  
87  import groovy.lang.GroovyClassLoader;
88  import groovy.lang.GroovyRuntimeException;
89  
90  /***
91   * Collects all compilation data as it is generated by the compiler system.
92   * Allows additional source units to be added and compilation run again (to
93   * affect only the deltas).
94   *
95   * @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a>
96   * @author <a href="mailto:blackdrag@gmx.org">Jochen Theodorou</a>
97   * @version $Id: CompilationUnit.java 4661 2007-01-02 16:52:26Z blackdrag $
98   */
99  
100 public class CompilationUnit extends ProcessingUnit {
101 
102 
103     //---------------------------------------------------------------------------
104     // CONSTRUCTION AND SUCH
105 
106 
107     protected HashMap sources;    // The SourceUnits from which this unit is built
108     protected Map summariesBySourceName;      // Summary of each SourceUnit
109     protected Map summariesByPublicClassName;       // Summary of each SourceUnit
110     protected Map classSourcesByPublicClassName;    // Summary of each Class
111     protected ArrayList names;      // Names for each SourceUnit in sources.
112     protected LinkedList queuedSources;
113     
114     
115     protected CompileUnit ast;        // The overall AST for this CompilationUnit.
116     protected ArrayList generatedClasses;    // The classes generated during classgen.
117 
118 
119     protected Verifier verifier;   // For use by verify().
120 
121     
122     protected boolean debug;      // Controls behaviour of classgen() and other routines.
123     protected boolean configured; // Set true after the first configure() operation
124 
125 
126     protected ClassgenCallback classgenCallback;  // A callback for use during classgen()
127     protected ProgressCallback progressCallback;  // A callback for use during compile()
128     protected ResolveVisitor resolveVisitor;
129 
130     LinkedList[] phaseOperations;
131     
132 
133     /***
134      * Initializes the CompilationUnit with defaults.
135      */
136     public CompilationUnit() {
137         this(null, null, null);
138     }
139 
140 
141 
142     /***
143      * Initializes the CompilationUnit with defaults except for class loader.
144      */
145     public CompilationUnit(GroovyClassLoader loader) {
146         this(null, null, loader);
147     }
148 
149 
150 
151     /***
152      * Initializes the CompilationUnit with no security considerations.
153      */
154     public CompilationUnit(CompilerConfiguration configuration) {
155         this(configuration, null, null);
156     }
157 
158     /***
159      * Initializes the CompilationUnit with a CodeSource for controlling
160      * security stuff and a class loader for loading classes.
161      */
162     public CompilationUnit(CompilerConfiguration configuration, CodeSource security, GroovyClassLoader loader) {
163         super(configuration, loader, null);
164         this.names = new ArrayList();
165         this.queuedSources = new LinkedList();
166         this.sources = new HashMap();
167         this.summariesBySourceName = new HashMap();
168         this.summariesByPublicClassName = new HashMap();
169         this.classSourcesByPublicClassName = new HashMap();
170         
171         this.ast = new CompileUnit(this.classLoader, security, this.configuration);
172         this.generatedClasses = new ArrayList();
173 
174 
175         this.verifier = new Verifier();
176         this.resolveVisitor = new ResolveVisitor(this);
177         
178         phaseOperations = new LinkedList[Phases.ALL+1];
179         for (int i=0; i<phaseOperations.length; i++) {
180             phaseOperations[i] = new LinkedList();
181         }
182         addPhaseOperation(new SourceUnitOperation() {
183             public void call(SourceUnit source) throws CompilationFailedException {
184                 source.parse();
185             }
186         }, Phases.PARSING);
187         addPhaseOperation(summarize, Phases.PARSING);
188         addPhaseOperation(convert,   Phases.CONVERSION);
189         addPhaseOperation(resolve,   Phases.SEMANTIC_ANALYSIS);
190         addPhaseOperation(compileCompleteCheck, Phases.CANONICALIZATION);
191         addPhaseOperation(classgen,  Phases.CLASS_GENERATION);
192         addPhaseOperation(output);
193         
194         this.classgenCallback = null;
195     }
196     
197     
198     
199     
200     
201     public void addPhaseOperation(SourceUnitOperation op, int phase) {
202         if (phase<0 || phase>Phases.ALL) throw new IllegalArgumentException("phase "+phase+" is unknown");
203         phaseOperations[phase].add(op);
204     }
205     
206     public void addPhaseOperation(PrimaryClassNodeOperation op, int phase) {
207         if (phase<0 || phase>Phases.ALL) throw new IllegalArgumentException("phase "+phase+" is unknown");
208         phaseOperations[phase].add(op);        
209     }
210     
211     public void addPhaseOperation(GroovyClassOperation op) {
212         phaseOperations[Phases.OUTPUT].addFirst(op);
213     }
214     
215 
216     /***
217      * Configures its debugging mode and classloader classpath from a given compiler configuration.
218      * This cannot be done more than once due to limitations in {@link java.net.URLClassLoader URLClassLoader}.
219      */
220     public void configure(CompilerConfiguration configuration) {
221         super.configure(configuration);
222         this.debug = configuration.getDebug();
223 
224         if (!this.configured && this.classLoader instanceof GroovyClassLoader) {
225             appendCompilerConfigurationClasspathToClassLoader(configuration, (GroovyClassLoader) this.classLoader);
226         }
227 
228         this.configured = true;
229     }
230 
231     private void appendCompilerConfigurationClasspathToClassLoader(CompilerConfiguration configuration, GroovyClassLoader classLoader) {
232         /*for (Iterator iterator = configuration.getClasspath().iterator(); iterator.hasNext(); ) {
233             classLoader.addClasspath((String) iterator.next());
234         }*/
235     }
236 
237     /***
238      * Returns the CompileUnit that roots our AST.
239      */
240     public CompileUnit getAST() {
241         return this.ast;
242     }
243 
244     /***
245      * Get the source summaries
246      */
247     public Map getSummariesBySourceName() {
248         return summariesBySourceName;
249     }
250     public Map getSummariesByPublicClassName() {
251         return summariesByPublicClassName;
252     }
253     public Map getClassSourcesByPublicClassName() {
254         return classSourcesByPublicClassName;
255     }
256 
257     public boolean isPublicClass(String className) {
258         return summariesByPublicClassName.containsKey(className);
259     }
260     
261     
262     /***
263      * Get the GroovyClasses generated by compile().
264      */
265     public List getClasses() {
266         return generatedClasses;
267     }
268 
269 
270     /***
271      * Convenience routine to get the first ClassNode, for
272      * when you are sure there is only one.
273      */
274     public ClassNode getFirstClassNode() {
275         return (ClassNode) ((ModuleNode) this.ast.getModules().get(0)).getClasses().get(0);
276     }
277 
278 
279     /***
280      * Convenience routine to get the named ClassNode.
281      */
282     public ClassNode getClassNode(final String name) {
283         final ClassNode[] result = new ClassNode[]{null};
284         PrimaryClassNodeOperation handler = new PrimaryClassNodeOperation() {
285             public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) {
286                 if (classNode.getName().equals(name)) {
287                     result[0] = classNode;
288                 }
289             }
290         };
291 
292         try {
293             applyToPrimaryClassNodes(handler);
294         } catch (CompilationFailedException e) {
295             if (debug) e.printStackTrace();
296         }
297         return result[0];
298     }
299 
300 
301 
302 
303 
304     //---------------------------------------------------------------------------
305     // SOURCE CREATION
306 
307 
308     /***
309      * Adds a set of file paths to the unit.
310      */
311     public void addSources(String[] paths) {
312         for (int i = 0; i < paths.length; i++) {
313             File file = new File(paths[i]);
314             addSource(file);
315         }
316     }
317 
318 
319     /***
320      * Adds a set of source files to the unit.
321      */
322     public void addSources(File[] files) {
323         for (int i = 0; i < files.length; i++) {
324             addSource(files[i]);
325         }
326     }
327 
328 
329     /***
330      * Adds a source file to the unit.
331      */
332     public SourceUnit addSource(File file) {
333         return addSource(new SourceUnit(file, configuration, classLoader, getErrorCollector()));
334     }
335     
336     /***
337      * Adds a source file to the unit.
338      */
339     public SourceUnit addSource(URL url) {
340         return addSource(new SourceUnit(url, configuration, classLoader,getErrorCollector()));
341     }
342 
343 
344     /***
345      * Adds a InputStream source to the unit.
346      */
347     public SourceUnit addSource(String name, InputStream stream) {
348         ReaderSource source = new InputStreamReaderSource(stream, configuration);
349         return addSource(new SourceUnit(name, source, configuration, classLoader, getErrorCollector()));
350     }
351 
352 
353     /***
354      * Adds a SourceUnit to the unit.
355      */
356     public SourceUnit addSource(SourceUnit source) {
357         String name = source.getName();
358         source.setClassLoader(this.classLoader);
359         for (Iterator iter = queuedSources.iterator(); iter.hasNext();) {
360 			SourceUnit su = (SourceUnit) iter.next();
361 			if (name.equals(su.getName())) return su;
362 		}
363         queuedSources.add(source);
364         return source;
365     }
366 
367 
368     /***
369      * Returns an iterator on the unit's SourceUnits.
370      */
371     public Iterator iterator() {
372         return new Iterator() {
373             Iterator nameIterator = names.iterator();
374 
375 
376             public boolean hasNext() {
377                 return nameIterator.hasNext();
378             }
379 
380 
381             public Object next() {
382                 String name = (String) nameIterator.next();
383                 return sources.get(name);
384             }
385 
386 
387             public void remove() {
388                 throw new UnsupportedOperationException();
389             }
390         };
391     }
392 
393 
394     /***
395      * Adds a ClassNode directly to the unit (ie. without source).
396      * WARNING: the source is needed for error reporting, using
397      *          this method without setting a SourceUnit will cause
398      *          NullPinterExceptions
399      */
400     public void addClassNode(ClassNode node) {
401         ModuleNode module = new ModuleNode(this.ast);
402         this.ast.addModule(module);
403         module.addClass(node);
404     }
405 
406 
407     //---------------------------------------------------------------------------
408     // EXTERNAL CALLBACKS
409 
410 
411     /***
412      * A callback interface you can use to "accompany" the classgen()
413      * code as it traverses the ClassNode tree.  You will be called-back
414      * for each primary and inner class.  Use setClassgenCallback() before
415      * running compile() to set your callback.
416      */
417     public static abstract class ClassgenCallback {
418         public abstract void call(ClassVisitor writer, ClassNode node) throws CompilationFailedException;
419     }
420 
421 
422     /***
423      * Sets a ClassgenCallback.  You can have only one, and setting
424      * it to null removes any existing setting.
425      */
426     public void setClassgenCallback(ClassgenCallback visitor) {
427         this.classgenCallback = visitor;
428     }
429 
430 
431     /***
432      * A callback interface you can use to get a callback after every
433      * unit of the compile process.  You will be called-back with a
434      * ProcessingUnit and a phase indicator.  Use setProgressCallback()
435      * before running compile() to set your callback.
436      */
437     public static abstract class ProgressCallback {
438 
439         public abstract void call(ProcessingUnit context, int phase) throws CompilationFailedException;
440     }
441 
442     /***
443      * Sets a ProgressCallback.  You can have only one, and setting
444      * it to null removes any existing setting.
445      */
446     public void setProgressCallback(ProgressCallback callback) {
447         this.progressCallback = callback;
448     }
449 
450 
451     //---------------------------------------------------------------------------
452     // ACTIONS
453 
454 
455     /***
456      * Synonym for compile(Phases.ALL).
457      */
458     public void compile() throws CompilationFailedException {
459         compile(Phases.ALL);
460     }
461 
462     /***
463      * Compiles the compilation unit from sources.
464      */
465     public void compile(int throughPhase) throws CompilationFailedException {
466         //
467         // To support delta compilations, we always restart
468         // the compiler.  The individual passes are responsible
469         // for not reprocessing old code.
470         gotoPhase(Phases.INITIALIZATION);
471         throughPhase = Math.min(throughPhase,Phases.ALL);
472 
473         while (throughPhase >= phase && phase <= Phases.ALL) {
474             
475             for (Iterator it = phaseOperations[phase].iterator(); it.hasNext();) {
476                 Object operation = it.next();
477                 if (operation instanceof PrimaryClassNodeOperation) {
478                     applyToPrimaryClassNodes((PrimaryClassNodeOperation) operation);
479                 } else if (operation instanceof SourceUnitOperation) {
480                     applyToSourceUnits((SourceUnitOperation)operation);
481                 } else {
482                     applyToGeneratedGroovyClasses((GroovyClassOperation)operation);
483                 }
484             }
485             
486             if (dequeued()) continue;
487            
488             if (progressCallback != null) progressCallback.call(this, phase);
489             completePhase();
490             applyToSourceUnits(mark);
491             
492             gotoPhase(phase+1);
493             
494             if (phase==Phases.CLASS_GENERATION) {
495                 sortClasses();
496             }
497         }
498             
499         errorCollector.failIfErrors();
500     }
501     
502     private void sortClasses() throws CompilationFailedException {
503         Iterator modules = this.ast.getModules().iterator();
504         while (modules.hasNext()) {
505             ModuleNode module = (ModuleNode) modules.next();
506             
507             // before we actually do the sorting we should check
508             // for cyclic references
509             List classes = module.getClasses();
510             for (Iterator iter = classes.iterator(); iter.hasNext();) {
511                 ClassNode start = (ClassNode) iter.next();
512                 ClassNode cn = start;
513                 HashSet parents = new HashSet();
514                 do {
515                     if (parents.contains(cn.getName())) {
516                         getErrorCollector().addErrorAndContinue(
517                                 new SimpleMessage("cyclic inheritance involving "+cn.getName()+" in class "+start.getName(),this)
518                         );
519                         cn=null;
520                     } else {
521                         parents.add(cn.getName());
522                         cn = cn.getSuperClass();
523                     }
524                 } while (cn!=null);
525             }
526             errorCollector.failIfErrors();
527             module.sortClasses();
528             
529         }
530     }
531     
532     
533     /***
534      * Dequeues any source units add through addSource and resets the compiler phase
535      * to initialization. 
536      * 
537      * Note: this does not mean a file is recompiled. If a SoucreUnit has already passed
538      * a phase it is skipped until a higher phase is reached. 
539      * @return TODO
540      * 
541      * @throws CompilationFailedException
542      */    
543     protected boolean dequeued() throws CompilationFailedException {
544         boolean dequeue = !queuedSources.isEmpty();
545         while (!queuedSources.isEmpty()) {
546             SourceUnit su = (SourceUnit) queuedSources.removeFirst();
547             String name = su.getName();
548             names.add(name);
549             sources.put(name,su);
550         }
551         if (dequeue) {
552             gotoPhase(Phases.INITIALIZATION);
553         }
554         return dequeue;
555     }
556 
557 
558     /***
559      * Adds summary of each class to maps
560      */
561     private SourceUnitOperation summarize = new SourceUnitOperation() {
562         public void call(SourceUnit source) throws CompilationFailedException {
563             SourceSummary sourceSummary = source.getSourceSummary();
564             if (sourceSummary != null) {
565                 summariesBySourceName.put(source.getName(),sourceSummary);
566                 List publicClassSources = sourceSummary.getPublicClassSources();
567                 if (publicClassSources == null || publicClassSources.size() == 0) {
568                     //todo - is this the best way to handle scripts?
569                     summariesByPublicClassName.put("*NoName*",sourceSummary);
570                     // nothing to put into classSourcesByClassName as no ClassSource
571                 } else {
572                     Iterator itr = publicClassSources.iterator();
573                     while (itr.hasNext()) {
574                         ClassSource classSource = (ClassSource)itr.next();
575                         summariesByPublicClassName.put(classSource.getName(),sourceSummary);
576                         classSourcesByPublicClassName.put(classSource.getName(),classSource);
577                     }
578                 }
579             }
580         }
581     };
582     
583     /***
584      * Resolves all types
585      */
586     private SourceUnitOperation resolve = new SourceUnitOperation() {
587         public void call(SourceUnit source) throws CompilationFailedException {
588             List classes = source.ast.getClasses();
589             for (Iterator it = classes.iterator(); it.hasNext();) {
590                 ClassNode node = (ClassNode) it.next();
591                 
592                 VariableScopeVisitor scopeVisitor = new VariableScopeVisitor(source);
593                 scopeVisitor.visitClass(node);
594                 
595                 resolveVisitor.startResolving(node,source);
596             }
597             
598         }
599     };
600     
601     /***
602      * Runs convert() on a single SourceUnit.
603      */
604     private SourceUnitOperation convert = new SourceUnitOperation() {
605         public void call(SourceUnit source) throws CompilationFailedException {
606             source.convert();
607             CompilationUnit.this.ast.addModule(source.getAST());
608 
609 
610             if (CompilationUnit.this.progressCallback != null) {
611                 CompilationUnit.this.progressCallback.call(source, CompilationUnit.this.phase);
612             }
613         }
614     };
615     
616     private GroovyClassOperation output = new GroovyClassOperation() {
617         public void call(GroovyClass gclass) throws CompilationFailedException {
618             boolean failures = false;
619             String name = gclass.getName().replace('.', File.separatorChar) + ".class";
620             File path = new File(configuration.getTargetDirectory(), name);
621             
622             //
623             // Ensure the path is ready for the file
624             //
625             File directory = path.getParentFile();
626             if (directory != null && !directory.exists()) {
627                 directory.mkdirs();
628             }
629             
630             //
631             // Create the file and write out the data
632             //
633             byte[] bytes = gclass.getBytes();
634             
635             FileOutputStream stream = null;
636             try {
637                 stream = new FileOutputStream(path);
638                 stream.write(bytes, 0, bytes.length);
639             } catch (IOException e) {
640                 getErrorCollector().addError(Message.create(e.getMessage(),CompilationUnit.this));
641                 failures = true;
642             } finally {
643                 if (stream != null) {
644                     try {
645                         stream.close();
646                     } catch (Exception e) {
647                     }
648                 }
649             }            
650         }
651     };
652     
653     /* checks if all needed classes are compiled before generating the bytecode */
654     private SourceUnitOperation compileCompleteCheck = new SourceUnitOperation() {
655         public void call(SourceUnit source) throws CompilationFailedException {
656             List classes = source.ast.getClasses();
657             for (Iterator it = classes.iterator(); it.hasNext();) {
658                 ClassNode node = (ClassNode) it.next();
659                 CompileUnit cu = node.getCompileUnit();
660                 for (Iterator iter = cu.iterateClassNodeToCompile(); iter.hasNext();) {
661                     String name = (String) iter.next();
662                     SourceUnit su = ast.getScriptSourceLocation(name);
663                     List classesInSourceUnit = su.ast.getClasses();
664                     StringBuffer message = new StringBuffer();
665                     message
666                     .append ("Compilation incomplete: expected to find the class ")
667                     .append (name)
668                     .append (" in ")
669                     .append (su.getName());
670                     if (classesInSourceUnit.size()==0) {
671                         message.append(", but the file seems not to contain any classes");
672                     } else {
673                         message.append(", but the file contains the classes: ");
674                         boolean first = true;
675                         for (Iterator suClassesIter = classesInSourceUnit
676                                 .iterator(); suClassesIter.hasNext();) {
677                             ClassNode cn = (ClassNode) suClassesIter.next();
678                             if (!first) {
679                                 message.append(", ");
680                             } else {
681                                 first=false;
682                             }
683                             message.append(cn.getName());                                
684                         }
685                     }
686                     
687                     getErrorCollector().addErrorAndContinue(
688                             new SimpleMessage(message.toString(),CompilationUnit.this)
689                     );
690                     iter.remove();
691                 } 
692             }
693         }
694     };
695     
696 
697     /***
698      * Runs classgen() on a single ClassNode.
699      */
700     private PrimaryClassNodeOperation classgen = new PrimaryClassNodeOperation() {
701         public boolean needSortedInput() {
702             return true;
703         }
704         public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException {
705 
706         	//
707             // Run the Verifier on the outer class
708             //
709             try {
710                 verifier.visitClass(classNode);
711             } catch (GroovyRuntimeException rpe) {
712                 ASTNode node = rpe.getNode();
713                 getErrorCollector().addError(
714                         new SyntaxException(rpe.getMessage(),null,node.getLineNumber(),node.getColumnNumber()),
715                         source
716                 );
717             }
718             
719             LabelVerifier lv = new LabelVerifier(source);
720             lv.visitClass(classNode);
721 
722             ClassCompletionVerifier completionVerifier = new ClassCompletionVerifier(source);
723             completionVerifier.visitClass(classNode);
724             
725             // because the class may be generated even if a error was found
726             // and that class may have an invalid format we fail here if needed
727             getErrorCollector().failIfErrors();
728             
729             //
730             // Prep the generator machinery
731             //
732             ClassVisitor visitor = createClassVisitor();
733 
734 
735             String sourceName = (source == null ? classNode.getModule().getDescription() : source.getName());
736             // only show the file name and its extension like javac does in its stacktraces rather than the full path
737             // also takes care of both \ and / depending on the host compiling environment
738             if (sourceName != null)
739                 sourceName = sourceName.substring(Math.max(sourceName.lastIndexOf('//'), sourceName.lastIndexOf('/')) + 1);
740             ClassGenerator generator = new AsmClassGenerator(context, visitor, classLoader, sourceName);
741 
742 
743             //
744             // Run the generation and create the class (if required)
745             //
746             generator.visitClass(classNode);
747  
748 
749             byte[] bytes = ((ClassWriter) visitor).toByteArray();
750             generatedClasses.add(new GroovyClass(classNode.getName(), bytes));
751 
752 
753             //
754             // Handle any callback that's been set
755             //
756             if (CompilationUnit.this.classgenCallback != null) {
757                 classgenCallback.call(visitor, classNode);
758             }
759 
760 
761             //
762             // Recurse for inner classes
763             //
764             LinkedList innerClasses = generator.getInnerClasses();
765             while (!innerClasses.isEmpty()) {
766                 classgen.call(source, context, (ClassNode) innerClasses.removeFirst());
767             }
768         }
769     };
770 
771 
772     protected ClassVisitor createClassVisitor() {
773         return new ClassWriter(true);
774     }
775 
776     //---------------------------------------------------------------------------
777     // PHASE HANDLING
778 
779 
780     /***
781      * Updates the phase marker on all sources.
782      */
783     protected void mark() throws CompilationFailedException {
784         applyToSourceUnits(mark);
785     }
786 
787 
788     /***
789      * Marks a single SourceUnit with the current phase,
790      * if it isn't already there yet.
791      */
792     private SourceUnitOperation mark = new SourceUnitOperation() {
793         public void call(SourceUnit source) throws CompilationFailedException {
794             if (source.phase < phase) {
795                 source.gotoPhase(phase);
796             }
797 
798 
799             if (source.phase == phase && phaseComplete && !source.phaseComplete) {
800                 source.completePhase();
801             }
802         }
803     };
804 
805 
806 
807 
808 
809     //---------------------------------------------------------------------------
810     // LOOP SIMPLIFICATION FOR SourceUnit OPERATIONS
811 
812 
813     /***
814      * An callback interface for use in the applyToSourceUnits loop driver.
815      */
816     public static abstract class SourceUnitOperation {
817         public abstract void call(SourceUnit source) throws CompilationFailedException;
818     }
819   
820 
821     /***
822      * A loop driver for applying operations to all SourceUnits.
823      * Automatically skips units that have already been processed
824      * through the current phase.
825      */
826     public void applyToSourceUnits(SourceUnitOperation body) throws CompilationFailedException {
827         Iterator keys = names.iterator();
828         while (keys.hasNext()) {
829             String name = (String) keys.next();
830             SourceUnit source = (SourceUnit) sources.get(name);
831             if ( (source.phase < phase) || (source.phase == phase && !source.phaseComplete)) {
832                 try {
833                     body.call(source);
834                 } catch (CompilationFailedException e) {
835                     throw e;
836                 } catch (Exception e) {
837                     GroovyBugError gbe = new GroovyBugError(e);
838                     changeBugText(gbe,source);
839                     throw gbe;
840                 } catch (GroovyBugError e) {
841                     changeBugText(e,source);
842                     throw e;
843                 }
844             }
845         }
846 
847 
848         getErrorCollector().failIfErrors();
849     }
850 
851 
852     //---------------------------------------------------------------------------
853     // LOOP SIMPLIFICATION FOR PRIMARY ClassNode OPERATIONS
854 
855 
856 
857     /***
858      * An callback interface for use in the applyToSourceUnits loop driver.
859      */
860     public static abstract class PrimaryClassNodeOperation {
861         public abstract void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException;
862         public boolean needSortedInput(){
863             return false;
864         }
865     }
866 
867     public static abstract class GroovyClassOperation {
868         public abstract void call(GroovyClass gclass) throws CompilationFailedException;
869     }
870 
871     private List getPrimaryClassNodes(boolean sort) {
872         ArrayList unsorted = new ArrayList();
873         Iterator modules = this.ast.getModules().iterator();
874         while (modules.hasNext()) {
875             ModuleNode module = (ModuleNode) modules.next();
876 
877             Iterator classNodes = module.getClasses().iterator();
878             while (classNodes.hasNext()) {
879                 ClassNode classNode = (ClassNode) classNodes.next();
880                 unsorted.add(classNode);
881             }
882         }
883         
884         if(sort==false) return unsorted;
885         
886         int[] indexClass = new int[unsorted.size()];
887         int[] indexInterface = new int[unsorted.size()];
888         {            
889             int i = 0;
890             for (Iterator iter = unsorted.iterator(); iter.hasNext(); i++) {
891                 ClassNode node = (ClassNode) iter.next();
892                 int count = 0;
893                 ClassNode element = node;
894                 while (element!=null){
895                     count++;
896                     element = element.getSuperClass();
897                 }
898                 if (node.isInterface()) {
899                     indexInterface[i] = count;
900                     indexClass[i] = -1;
901                 } else {
902                     indexClass[i] = count;
903                     indexInterface[i] = -1;
904                 }
905             }
906         }
907         
908         List sorted = getSorted(indexInterface,unsorted);
909         sorted.addAll(getSorted(indexClass,unsorted));
910         
911         return sorted;
912     }
913     
914     private List getSorted(int[] index, List unsorted) {
915         ArrayList sorted = new ArrayList(unsorted.size());
916         int start = 0;
917         for (int i=0; i<unsorted.size(); i++) {           
918             int min = -1;
919             for (int j=0; j<unsorted.size(); j++) {
920                 if (index[j]==-1) continue;
921                 if (min==-1) {
922                     min = j;
923                 } else if (index[j]<index[min]) {
924                     min = j;
925                 }
926             }
927             if (min==-1) break;
928             sorted.add(unsorted.get(min));
929             index[min] = -1;
930         }
931         return sorted;
932     }
933 
934     /***
935      * A loop driver for applying operations to all primary ClassNodes in
936      * our AST.  Automatically skips units that have already been processed
937      * through the current phase.
938      */
939     public void applyToPrimaryClassNodes(PrimaryClassNodeOperation body) throws CompilationFailedException {
940         Iterator classNodes = getPrimaryClassNodes(body.needSortedInput()).iterator();
941         while (classNodes.hasNext()) {
942             SourceUnit context=null;
943             try {
944                ClassNode classNode = (ClassNode) classNodes.next();
945                context = classNode.getModule().getContext();
946                if (context == null || context.phase <= phase) {
947                    body.call(context, new GeneratorContext(this.ast), classNode);
948                }
949             } catch (CompilationFailedException e) {
950                 // fall thorugh, getErrorREporter().failIfErrors() will triger
951             } catch (NullPointerException npe){
952                 throw npe;
953             } catch (GroovyBugError e) {
954                 changeBugText(e,context);
955                 throw e;
956             } catch (Exception e) {
957                 // check the exception for a nested compilation exception
958                 ErrorCollector nestedCollector = null;
959                 for (Throwable next = e.getCause(); next!=e && next!=null; next=next.getCause()) {
960                     if (!(next instanceof MultipleCompilationErrorsException)) continue;
961                     MultipleCompilationErrorsException mcee = (MultipleCompilationErrorsException) next;
962                     nestedCollector = mcee.collector;
963                     break;
964                 }
965 
966                 if (nestedCollector!=null) {
967                     getErrorCollector().addCollectorContents(nestedCollector);
968                 } else {
969                     getErrorCollector().addError(new ExceptionMessage(e,configuration.getDebug(),this));
970                 }
971             }
972         }
973 
974         getErrorCollector().failIfErrors();
975     }
976     
977     public void applyToGeneratedGroovyClasses(GroovyClassOperation body) throws CompilationFailedException {
978         if (this.phase != Phases.OUTPUT && !(this.phase == Phases.CLASS_GENERATION && this.phaseComplete)) {
979             throw new GroovyBugError("CompilationUnit not ready for output(). Current phase="+getPhaseDescription());
980         }
981 
982         boolean failures = false;
983 
984         Iterator iterator = this.generatedClasses.iterator();
985         while (iterator.hasNext()) {
986             //
987             // Get the class and calculate its filesystem name
988             //
989             GroovyClass gclass = (GroovyClass) iterator.next();
990             try {
991                 body.call(gclass);
992             } catch (CompilationFailedException e) {
993                 // fall thorugh, getErrorREporter().failIfErrors() will triger
994             } catch (NullPointerException npe){
995                 throw npe;
996             } catch (GroovyBugError e) {
997                 changeBugText(e,null);
998                 throw e;
999             } catch (Exception e) {
1000                 GroovyBugError gbe = new GroovyBugError(e);
1001                 throw gbe;
1002             }
1003         }
1004         
1005         getErrorCollector().failIfErrors();
1006     }
1007 
1008     private void changeBugText(GroovyBugError e, SourceUnit context) {
1009         e.setBugText("exception in phase '"+getPhaseDescription()+"' in source unit '"+((context!=null)?context.getName():"?")+"' "+e.getBugText());
1010     }
1011 }