001    /*
002     $Id: CompilationUnit.java 4354 2006-12-13 21:14:22Z blackdrag $
003    
004    
005     Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
006    
007    
008     Redistribution and use of this software and associated documentation
009     ("Software"), with or without modification, are permitted provided
010     that the following conditions are met:
011    
012     1. Redistributions of source code must retain copyright
013        statements and notices.  Redistributions must also contain a
014        copy of this document.
015    
016    
017     2. Redistributions in binary form must reproduce the
018        above copyright notice, this list of conditions and the
019        following disclaimer in the documentation and/or other
020        materials provided with the distribution.
021    
022    
023     3. The name "groovy" must not be used to endorse or promote
024        products derived from this Software without prior written
025        permission of The Codehaus.  For written permission,
026        please contact info@codehaus.org.
027    
028    
029     4. Products derived from this Software may not be called "groovy"
030        nor may "groovy" appear in their names without prior written
031        permission of The Codehaus. "groovy" is a registered
032        trademark of The Codehaus.
033    
034    
035     5. Due credit should be given to The Codehaus -
036        http://groovy.codehaus.org/
037    
038    
039     THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
040     ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
041     NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
042     FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
043     THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
044     INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
045     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
046     SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
047     HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
048     STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
049     ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
050     OF THE POSSIBILITY OF SUCH DAMAGE.
051     */
052    
053    
054    package org.codehaus.groovy.control;
055    
056    import java.io.File;
057    import java.io.FileOutputStream;
058    import java.io.IOException;
059    import java.io.InputStream;
060    import java.net.URL;
061    import java.security.CodeSource;
062    import java.util.*;
063    
064    import org.codehaus.groovy.GroovyBugError;
065    import org.codehaus.groovy.ast.ASTNode;
066    import org.codehaus.groovy.ast.ClassNode;
067    import org.codehaus.groovy.ast.CompileUnit;
068    import org.codehaus.groovy.ast.ModuleNode;
069    import org.codehaus.groovy.classgen.AsmClassGenerator;
070    import org.codehaus.groovy.classgen.ClassCompletionVerifier;
071    import org.codehaus.groovy.classgen.ClassGenerator;
072    import org.codehaus.groovy.classgen.GeneratorContext;
073    import org.codehaus.groovy.classgen.VariableScopeVisitor;
074    import org.codehaus.groovy.classgen.Verifier;
075    import org.codehaus.groovy.control.io.InputStreamReaderSource;
076    import org.codehaus.groovy.control.io.ReaderSource;
077    import org.codehaus.groovy.control.messages.ExceptionMessage;
078    import org.codehaus.groovy.control.messages.Message;
079    import org.codehaus.groovy.control.messages.SimpleMessage;
080    import org.codehaus.groovy.syntax.SyntaxException;
081    import org.codehaus.groovy.syntax.ClassSource;
082    import org.codehaus.groovy.syntax.SourceSummary;
083    import org.codehaus.groovy.tools.GroovyClass;
084    import org.objectweb.asm.ClassVisitor;
085    import org.objectweb.asm.ClassWriter;
086    
087    import groovy.lang.GroovyClassLoader;
088    import groovy.lang.GroovyRuntimeException;
089    
090    /**
091     * Collects all compilation data as it is generated by the compiler system.
092     * Allows additional source units to be added and compilation run again (to
093     * affect only the deltas).
094     *
095     * @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a>
096     * @author <a href="mailto:blackdrag@gmx.org">Jochen Theodorou</a>
097     * @version $Id: CompilationUnit.java 4354 2006-12-13 21:14:22Z blackdrag $
098     */
099    
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[] index = new int[unsorted.size()];
887            {            
888                int i = 0;
889                for (Iterator iter = unsorted.iterator(); iter.hasNext(); i++) {
890                    ClassNode element = (ClassNode) iter.next();
891                    int count = 0;
892                    while (element!=null){
893                        count++;
894                        element = element.getSuperClass();
895                    }
896                    index[i] = count;
897                }
898            }
899            
900            ArrayList sorted = new ArrayList(unsorted.size());
901            int start = 0;
902            for (int i=0; i<index.length; i++) {           
903                int min = -1;
904                for (int j=0; j<index.length; j++) {
905                    if (index[j]==-1) continue;
906                    if (min==-1) {
907                        min = j;
908                    } else if (index[j]<index[min]) {
909                        min = j;
910                    }
911                }
912                sorted.add(unsorted.get(min));
913                index[min] = -1;
914            }
915            
916            return sorted;
917        }
918    
919        /**
920         * A loop driver for applying operations to all primary ClassNodes in
921         * our AST.  Automatically skips units that have already been processed
922         * through the current phase.
923         */
924        public void applyToPrimaryClassNodes(PrimaryClassNodeOperation body) throws CompilationFailedException {
925            Iterator classNodes = getPrimaryClassNodes(body.needSortedInput()).iterator();
926            while (classNodes.hasNext()) {
927                SourceUnit context=null;
928                try {
929                   ClassNode classNode = (ClassNode) classNodes.next();
930                   context = classNode.getModule().getContext();
931                   if (context == null || context.phase <= phase) {
932                       body.call(context, new GeneratorContext(this.ast), classNode);
933                   }
934                } catch (CompilationFailedException e) {
935                    // fall thorugh, getErrorREporter().failIfErrors() will triger
936                } catch (NullPointerException npe){
937                    throw npe;
938                } catch (GroovyBugError e) {
939                    changeBugText(e,context);
940                    throw e;
941                } catch (Exception e) {
942                    // check the exception for a nested compilation exception
943                    ErrorCollector nestedCollector = null;
944                    for (Throwable next = e.getCause(); next!=e && next!=null; next=next.getCause()) {
945                        if (!(next instanceof MultipleCompilationErrorsException)) continue;
946                        MultipleCompilationErrorsException mcee = (MultipleCompilationErrorsException) next;
947                        nestedCollector = mcee.collector;
948                        break;
949                    }
950    
951                    if (nestedCollector!=null) {
952                        getErrorCollector().addCollectorContents(nestedCollector);
953                    } else {
954                        getErrorCollector().addError(new ExceptionMessage(e,configuration.getDebug(),this));
955                    }
956                }
957            }
958    
959            getErrorCollector().failIfErrors();
960        }
961        
962        public void applyToGeneratedGroovyClasses(GroovyClassOperation body) throws CompilationFailedException {
963            if (this.phase != Phases.OUTPUT && !(this.phase == Phases.CLASS_GENERATION && this.phaseComplete)) {
964                throw new GroovyBugError("CompilationUnit not ready for output(). Current phase="+getPhaseDescription());
965            }
966    
967            boolean failures = false;
968    
969            Iterator iterator = this.generatedClasses.iterator();
970            while (iterator.hasNext()) {
971                //
972                // Get the class and calculate its filesystem name
973                //
974                GroovyClass gclass = (GroovyClass) iterator.next();
975                try {
976                    body.call(gclass);
977                } catch (CompilationFailedException e) {
978                    // fall thorugh, getErrorREporter().failIfErrors() will triger
979                } catch (NullPointerException npe){
980                    throw npe;
981                } catch (GroovyBugError e) {
982                    changeBugText(e,null);
983                    throw e;
984                } catch (Exception e) {
985                    GroovyBugError gbe = new GroovyBugError(e);
986                    throw gbe;
987                }
988            }
989            
990            getErrorCollector().failIfErrors();
991        }
992    
993        private void changeBugText(GroovyBugError e, SourceUnit context) {
994            e.setBugText("exception in phase '"+getPhaseDescription()+"' in source unit '"+((context!=null)?context.getName():"?")+"' "+e.getBugText());
995        }
996    }