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