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 }