View Javadoc

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