View Javadoc

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