0001: /*
0002: $Id: CompilationUnit.java 4661 2007-01-02 16:52:26Z blackdrag $
0003:
0004:
0005: Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
0006:
0007:
0008: Redistribution and use of this software and associated documentation
0009: ("Software"), with or without modification, are permitted provided
0010: that the following conditions are met:
0011:
0012: 1. Redistributions of source code must retain copyright
0013: statements and notices. Redistributions must also contain a
0014: copy of this document.
0015:
0016:
0017: 2. Redistributions in binary form must reproduce the
0018: above copyright notice, this list of conditions and the
0019: following disclaimer in the documentation and/or other
0020: materials provided with the distribution.
0021:
0022:
0023: 3. The name "groovy" must not be used to endorse or promote
0024: products derived from this Software without prior written
0025: permission of The Codehaus. For written permission,
0026: please contact info@codehaus.org.
0027:
0028:
0029: 4. Products derived from this Software may not be called "groovy"
0030: nor may "groovy" appear in their names without prior written
0031: permission of The Codehaus. "groovy" is a registered
0032: trademark of The Codehaus.
0033:
0034:
0035: 5. Due credit should be given to The Codehaus -
0036: http://groovy.codehaus.org/
0037:
0038:
0039: THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
0040: ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
0041: NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
0042: FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
0043: THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
0044: INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
0045: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
0046: SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
0047: HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
0048: STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
0049: ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
0050: OF THE POSSIBILITY OF SUCH DAMAGE.
0051: */
0052:
0053: package org.codehaus.groovy.control;
0054:
0055: import java.io.File;
0056: import java.io.FileOutputStream;
0057: import java.io.IOException;
0058: import java.io.InputStream;
0059: import java.net.URL;
0060: import java.security.CodeSource;
0061: import java.util.*;
0062:
0063: import org.codehaus.groovy.GroovyBugError;
0064: import org.codehaus.groovy.ast.ASTNode;
0065: import org.codehaus.groovy.ast.ClassNode;
0066: import org.codehaus.groovy.ast.CompileUnit;
0067: import org.codehaus.groovy.ast.ModuleNode;
0068: import org.codehaus.groovy.classgen.AsmClassGenerator;
0069: import org.codehaus.groovy.classgen.ClassCompletionVerifier;
0070: import org.codehaus.groovy.classgen.ClassGenerator;
0071: import org.codehaus.groovy.classgen.GeneratorContext;
0072: import org.codehaus.groovy.classgen.VariableScopeVisitor;
0073: import org.codehaus.groovy.classgen.Verifier;
0074: import org.codehaus.groovy.control.io.InputStreamReaderSource;
0075: import org.codehaus.groovy.control.io.ReaderSource;
0076: import org.codehaus.groovy.control.messages.ExceptionMessage;
0077: import org.codehaus.groovy.control.messages.Message;
0078: import org.codehaus.groovy.control.messages.SimpleMessage;
0079: import org.codehaus.groovy.syntax.SyntaxException;
0080: import org.codehaus.groovy.syntax.ClassSource;
0081: import org.codehaus.groovy.syntax.SourceSummary;
0082: import org.codehaus.groovy.tools.GroovyClass;
0083: import org.objectweb.asm.ClassVisitor;
0084: import org.objectweb.asm.ClassWriter;
0085:
0086: import groovy.lang.GroovyClassLoader;
0087: import groovy.lang.GroovyRuntimeException;
0088:
0089: /**
0090: * Collects all compilation data as it is generated by the compiler system.
0091: * Allows additional source units to be added and compilation run again (to
0092: * affect only the deltas).
0093: *
0094: * @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a>
0095: * @author <a href="mailto:blackdrag@gmx.org">Jochen Theodorou</a>
0096: * @version $Id: CompilationUnit.java 4661 2007-01-02 16:52:26Z blackdrag $
0097: */
0098:
0099: public class CompilationUnit extends ProcessingUnit {
0100:
0101: //---------------------------------------------------------------------------
0102: // CONSTRUCTION AND SUCH
0103:
0104: protected HashMap sources; // The SourceUnits from which this unit is built
0105: protected Map summariesBySourceName; // Summary of each SourceUnit
0106: protected Map summariesByPublicClassName; // Summary of each SourceUnit
0107: protected Map classSourcesByPublicClassName; // Summary of each Class
0108: protected ArrayList names; // Names for each SourceUnit in sources.
0109: protected LinkedList queuedSources;
0110:
0111: protected CompileUnit ast; // The overall AST for this CompilationUnit.
0112: protected ArrayList generatedClasses; // The classes generated during classgen.
0113:
0114: protected Verifier verifier; // For use by verify().
0115:
0116: protected boolean debug; // Controls behaviour of classgen() and other routines.
0117: protected boolean configured; // Set true after the first configure() operation
0118:
0119: protected ClassgenCallback classgenCallback; // A callback for use during classgen()
0120: protected ProgressCallback progressCallback; // A callback for use during compile()
0121: protected ResolveVisitor resolveVisitor;
0122:
0123: LinkedList[] phaseOperations;
0124:
0125: /**
0126: * Initializes the CompilationUnit with defaults.
0127: */
0128: public CompilationUnit() {
0129: this (null, null, null);
0130: }
0131:
0132: /**
0133: * Initializes the CompilationUnit with defaults except for class loader.
0134: */
0135: public CompilationUnit(GroovyClassLoader loader) {
0136: this (null, null, loader);
0137: }
0138:
0139: /**
0140: * Initializes the CompilationUnit with no security considerations.
0141: */
0142: public CompilationUnit(CompilerConfiguration configuration) {
0143: this (configuration, null, null);
0144: }
0145:
0146: /**
0147: * Initializes the CompilationUnit with a CodeSource for controlling
0148: * security stuff and a class loader for loading classes.
0149: */
0150: public CompilationUnit(CompilerConfiguration configuration,
0151: CodeSource security, GroovyClassLoader loader) {
0152: super (configuration, loader, null);
0153: this .names = new ArrayList();
0154: this .queuedSources = new LinkedList();
0155: this .sources = new HashMap();
0156: this .summariesBySourceName = new HashMap();
0157: this .summariesByPublicClassName = new HashMap();
0158: this .classSourcesByPublicClassName = new HashMap();
0159:
0160: this .ast = new CompileUnit(this .classLoader, security,
0161: this .configuration);
0162: this .generatedClasses = new ArrayList();
0163:
0164: this .verifier = new Verifier();
0165: this .resolveVisitor = new ResolveVisitor(this );
0166:
0167: phaseOperations = new LinkedList[Phases.ALL + 1];
0168: for (int i = 0; i < phaseOperations.length; i++) {
0169: phaseOperations[i] = new LinkedList();
0170: }
0171: addPhaseOperation(new SourceUnitOperation() {
0172: public void call(SourceUnit source)
0173: throws CompilationFailedException {
0174: source.parse();
0175: }
0176: }, Phases.PARSING);
0177: addPhaseOperation(summarize, Phases.PARSING);
0178: addPhaseOperation(convert, Phases.CONVERSION);
0179: addPhaseOperation(resolve, Phases.SEMANTIC_ANALYSIS);
0180: addPhaseOperation(compileCompleteCheck, Phases.CANONICALIZATION);
0181: addPhaseOperation(classgen, Phases.CLASS_GENERATION);
0182: addPhaseOperation(output);
0183:
0184: this .classgenCallback = null;
0185: }
0186:
0187: public void addPhaseOperation(SourceUnitOperation op, int phase) {
0188: if (phase < 0 || phase > Phases.ALL)
0189: throw new IllegalArgumentException("phase " + phase
0190: + " is unknown");
0191: phaseOperations[phase].add(op);
0192: }
0193:
0194: public void addPhaseOperation(PrimaryClassNodeOperation op,
0195: int phase) {
0196: if (phase < 0 || phase > Phases.ALL)
0197: throw new IllegalArgumentException("phase " + phase
0198: + " is unknown");
0199: phaseOperations[phase].add(op);
0200: }
0201:
0202: public void addPhaseOperation(GroovyClassOperation op) {
0203: phaseOperations[Phases.OUTPUT].addFirst(op);
0204: }
0205:
0206: /**
0207: * Configures its debugging mode and classloader classpath from a given compiler configuration.
0208: * This cannot be done more than once due to limitations in {@link java.net.URLClassLoader URLClassLoader}.
0209: */
0210: public void configure(CompilerConfiguration configuration) {
0211: super .configure(configuration);
0212: this .debug = configuration.getDebug();
0213:
0214: if (!this .configured
0215: && this .classLoader instanceof GroovyClassLoader) {
0216: appendCompilerConfigurationClasspathToClassLoader(
0217: configuration, (GroovyClassLoader) this .classLoader);
0218: }
0219:
0220: this .configured = true;
0221: }
0222:
0223: private void appendCompilerConfigurationClasspathToClassLoader(
0224: CompilerConfiguration configuration,
0225: GroovyClassLoader classLoader) {
0226: /*for (Iterator iterator = configuration.getClasspath().iterator(); iterator.hasNext(); ) {
0227: classLoader.addClasspath((String) iterator.next());
0228: }*/
0229: }
0230:
0231: /**
0232: * Returns the CompileUnit that roots our AST.
0233: */
0234: public CompileUnit getAST() {
0235: return this .ast;
0236: }
0237:
0238: /**
0239: * Get the source summaries
0240: */
0241: public Map getSummariesBySourceName() {
0242: return summariesBySourceName;
0243: }
0244:
0245: public Map getSummariesByPublicClassName() {
0246: return summariesByPublicClassName;
0247: }
0248:
0249: public Map getClassSourcesByPublicClassName() {
0250: return classSourcesByPublicClassName;
0251: }
0252:
0253: public boolean isPublicClass(String className) {
0254: return summariesByPublicClassName.containsKey(className);
0255: }
0256:
0257: /**
0258: * Get the GroovyClasses generated by compile().
0259: */
0260: public List getClasses() {
0261: return generatedClasses;
0262: }
0263:
0264: /**
0265: * Convenience routine to get the first ClassNode, for
0266: * when you are sure there is only one.
0267: */
0268: public ClassNode getFirstClassNode() {
0269: return (ClassNode) ((ModuleNode) this .ast.getModules().get(0))
0270: .getClasses().get(0);
0271: }
0272:
0273: /**
0274: * Convenience routine to get the named ClassNode.
0275: */
0276: public ClassNode getClassNode(final String name) {
0277: final ClassNode[] result = new ClassNode[] { null };
0278: PrimaryClassNodeOperation handler = new PrimaryClassNodeOperation() {
0279: public void call(SourceUnit source,
0280: GeneratorContext context, ClassNode classNode) {
0281: if (classNode.getName().equals(name)) {
0282: result[0] = classNode;
0283: }
0284: }
0285: };
0286:
0287: try {
0288: applyToPrimaryClassNodes(handler);
0289: } catch (CompilationFailedException e) {
0290: if (debug)
0291: e.printStackTrace();
0292: }
0293: return result[0];
0294: }
0295:
0296: //---------------------------------------------------------------------------
0297: // SOURCE CREATION
0298:
0299: /**
0300: * Adds a set of file paths to the unit.
0301: */
0302: public void addSources(String[] paths) {
0303: for (int i = 0; i < paths.length; i++) {
0304: File file = new File(paths[i]);
0305: addSource(file);
0306: }
0307: }
0308:
0309: /**
0310: * Adds a set of source files to the unit.
0311: */
0312: public void addSources(File[] files) {
0313: for (int i = 0; i < files.length; i++) {
0314: addSource(files[i]);
0315: }
0316: }
0317:
0318: /**
0319: * Adds a source file to the unit.
0320: */
0321: public SourceUnit addSource(File file) {
0322: return addSource(new SourceUnit(file, configuration,
0323: classLoader, getErrorCollector()));
0324: }
0325:
0326: /**
0327: * Adds a source file to the unit.
0328: */
0329: public SourceUnit addSource(URL url) {
0330: return addSource(new SourceUnit(url, configuration,
0331: classLoader, getErrorCollector()));
0332: }
0333:
0334: /**
0335: * Adds a InputStream source to the unit.
0336: */
0337: public SourceUnit addSource(String name, InputStream stream) {
0338: ReaderSource source = new InputStreamReaderSource(stream,
0339: configuration);
0340: return addSource(new SourceUnit(name, source, configuration,
0341: classLoader, getErrorCollector()));
0342: }
0343:
0344: /**
0345: * Adds a SourceUnit to the unit.
0346: */
0347: public SourceUnit addSource(SourceUnit source) {
0348: String name = source.getName();
0349: source.setClassLoader(this .classLoader);
0350: for (Iterator iter = queuedSources.iterator(); iter.hasNext();) {
0351: SourceUnit su = (SourceUnit) iter.next();
0352: if (name.equals(su.getName()))
0353: return su;
0354: }
0355: queuedSources.add(source);
0356: return source;
0357: }
0358:
0359: /**
0360: * Returns an iterator on the unit's SourceUnits.
0361: */
0362: public Iterator iterator() {
0363: return new Iterator() {
0364: Iterator nameIterator = names.iterator();
0365:
0366: public boolean hasNext() {
0367: return nameIterator.hasNext();
0368: }
0369:
0370: public Object next() {
0371: String name = (String) nameIterator.next();
0372: return sources.get(name);
0373: }
0374:
0375: public void remove() {
0376: throw new UnsupportedOperationException();
0377: }
0378: };
0379: }
0380:
0381: /**
0382: * Adds a ClassNode directly to the unit (ie. without source).
0383: * WARNING: the source is needed for error reporting, using
0384: * this method without setting a SourceUnit will cause
0385: * NullPinterExceptions
0386: */
0387: public void addClassNode(ClassNode node) {
0388: ModuleNode module = new ModuleNode(this .ast);
0389: this .ast.addModule(module);
0390: module.addClass(node);
0391: }
0392:
0393: //---------------------------------------------------------------------------
0394: // EXTERNAL CALLBACKS
0395:
0396: /**
0397: * A callback interface you can use to "accompany" the classgen()
0398: * code as it traverses the ClassNode tree. You will be called-back
0399: * for each primary and inner class. Use setClassgenCallback() before
0400: * running compile() to set your callback.
0401: */
0402: public static abstract class ClassgenCallback {
0403: public abstract void call(ClassVisitor writer, ClassNode node)
0404: throws CompilationFailedException;
0405: }
0406:
0407: /**
0408: * Sets a ClassgenCallback. You can have only one, and setting
0409: * it to null removes any existing setting.
0410: */
0411: public void setClassgenCallback(ClassgenCallback visitor) {
0412: this .classgenCallback = visitor;
0413: }
0414:
0415: /**
0416: * A callback interface you can use to get a callback after every
0417: * unit of the compile process. You will be called-back with a
0418: * ProcessingUnit and a phase indicator. Use setProgressCallback()
0419: * before running compile() to set your callback.
0420: */
0421: public static abstract class ProgressCallback {
0422:
0423: public abstract void call(ProcessingUnit context, int phase)
0424: throws CompilationFailedException;
0425: }
0426:
0427: /**
0428: * Sets a ProgressCallback. You can have only one, and setting
0429: * it to null removes any existing setting.
0430: */
0431: public void setProgressCallback(ProgressCallback callback) {
0432: this .progressCallback = callback;
0433: }
0434:
0435: //---------------------------------------------------------------------------
0436: // ACTIONS
0437:
0438: /**
0439: * Synonym for compile(Phases.ALL).
0440: */
0441: public void compile() throws CompilationFailedException {
0442: compile(Phases.ALL);
0443: }
0444:
0445: /**
0446: * Compiles the compilation unit from sources.
0447: */
0448: public void compile(int throughPhase)
0449: throws CompilationFailedException {
0450: //
0451: // To support delta compilations, we always restart
0452: // the compiler. The individual passes are responsible
0453: // for not reprocessing old code.
0454: gotoPhase(Phases.INITIALIZATION);
0455: throughPhase = Math.min(throughPhase, Phases.ALL);
0456:
0457: while (throughPhase >= phase && phase <= Phases.ALL) {
0458:
0459: for (Iterator it = phaseOperations[phase].iterator(); it
0460: .hasNext();) {
0461: Object operation = it.next();
0462: if (operation instanceof PrimaryClassNodeOperation) {
0463: applyToPrimaryClassNodes((PrimaryClassNodeOperation) operation);
0464: } else if (operation instanceof SourceUnitOperation) {
0465: applyToSourceUnits((SourceUnitOperation) operation);
0466: } else {
0467: applyToGeneratedGroovyClasses((GroovyClassOperation) operation);
0468: }
0469: }
0470:
0471: if (dequeued())
0472: continue;
0473:
0474: if (progressCallback != null)
0475: progressCallback.call(this , phase);
0476: completePhase();
0477: applyToSourceUnits(mark);
0478:
0479: gotoPhase(phase + 1);
0480:
0481: if (phase == Phases.CLASS_GENERATION) {
0482: sortClasses();
0483: }
0484: }
0485:
0486: errorCollector.failIfErrors();
0487: }
0488:
0489: private void sortClasses() throws CompilationFailedException {
0490: Iterator modules = this .ast.getModules().iterator();
0491: while (modules.hasNext()) {
0492: ModuleNode module = (ModuleNode) modules.next();
0493:
0494: // before we actually do the sorting we should check
0495: // for cyclic references
0496: List classes = module.getClasses();
0497: for (Iterator iter = classes.iterator(); iter.hasNext();) {
0498: ClassNode start = (ClassNode) iter.next();
0499: ClassNode cn = start;
0500: HashSet parents = new HashSet();
0501: do {
0502: if (parents.contains(cn.getName())) {
0503: getErrorCollector()
0504: .addErrorAndContinue(
0505: new SimpleMessage(
0506: "cyclic inheritance involving "
0507: + cn.getName()
0508: + " in class "
0509: + start
0510: .getName(),
0511: this ));
0512: cn = null;
0513: } else {
0514: parents.add(cn.getName());
0515: cn = cn.getSuperClass();
0516: }
0517: } while (cn != null);
0518: }
0519: errorCollector.failIfErrors();
0520: module.sortClasses();
0521:
0522: }
0523: }
0524:
0525: /**
0526: * Dequeues any source units add through addSource and resets the compiler phase
0527: * to initialization.
0528: *
0529: * Note: this does not mean a file is recompiled. If a SoucreUnit has already passed
0530: * a phase it is skipped until a higher phase is reached.
0531: * @return TODO
0532: *
0533: * @throws CompilationFailedException
0534: */
0535: protected boolean dequeued() throws CompilationFailedException {
0536: boolean dequeue = !queuedSources.isEmpty();
0537: while (!queuedSources.isEmpty()) {
0538: SourceUnit su = (SourceUnit) queuedSources.removeFirst();
0539: String name = su.getName();
0540: names.add(name);
0541: sources.put(name, su);
0542: }
0543: if (dequeue) {
0544: gotoPhase(Phases.INITIALIZATION);
0545: }
0546: return dequeue;
0547: }
0548:
0549: /**
0550: * Adds summary of each class to maps
0551: */
0552: private SourceUnitOperation summarize = new SourceUnitOperation() {
0553: public void call(SourceUnit source)
0554: throws CompilationFailedException {
0555: SourceSummary sourceSummary = source.getSourceSummary();
0556: if (sourceSummary != null) {
0557: summariesBySourceName.put(source.getName(),
0558: sourceSummary);
0559: List publicClassSources = sourceSummary
0560: .getPublicClassSources();
0561: if (publicClassSources == null
0562: || publicClassSources.size() == 0) {
0563: //todo - is this the best way to handle scripts?
0564: summariesByPublicClassName.put("*NoName*",
0565: sourceSummary);
0566: // nothing to put into classSourcesByClassName as no ClassSource
0567: } else {
0568: Iterator itr = publicClassSources.iterator();
0569: while (itr.hasNext()) {
0570: ClassSource classSource = (ClassSource) itr
0571: .next();
0572: summariesByPublicClassName.put(classSource
0573: .getName(), sourceSummary);
0574: classSourcesByPublicClassName.put(classSource
0575: .getName(), classSource);
0576: }
0577: }
0578: }
0579: }
0580: };
0581:
0582: /**
0583: * Resolves all types
0584: */
0585: private SourceUnitOperation resolve = new SourceUnitOperation() {
0586: public void call(SourceUnit source)
0587: throws CompilationFailedException {
0588: List classes = source.ast.getClasses();
0589: for (Iterator it = classes.iterator(); it.hasNext();) {
0590: ClassNode node = (ClassNode) it.next();
0591:
0592: VariableScopeVisitor scopeVisitor = new VariableScopeVisitor(
0593: source);
0594: scopeVisitor.visitClass(node);
0595:
0596: resolveVisitor.startResolving(node, source);
0597: }
0598:
0599: }
0600: };
0601:
0602: /**
0603: * Runs convert() on a single SourceUnit.
0604: */
0605: private SourceUnitOperation convert = new SourceUnitOperation() {
0606: public void call(SourceUnit source)
0607: throws CompilationFailedException {
0608: source.convert();
0609: CompilationUnit.this .ast.addModule(source.getAST());
0610:
0611: if (CompilationUnit.this .progressCallback != null) {
0612: CompilationUnit.this .progressCallback.call(source,
0613: CompilationUnit.this .phase);
0614: }
0615: }
0616: };
0617:
0618: private GroovyClassOperation output = new GroovyClassOperation() {
0619: public void call(GroovyClass gclass)
0620: throws CompilationFailedException {
0621: boolean failures = false;
0622: String name = gclass.getName().replace('.',
0623: File.separatorChar)
0624: + ".class";
0625: File path = new File(configuration.getTargetDirectory(),
0626: name);
0627:
0628: //
0629: // Ensure the path is ready for the file
0630: //
0631: File directory = path.getParentFile();
0632: if (directory != null && !directory.exists()) {
0633: directory.mkdirs();
0634: }
0635:
0636: //
0637: // Create the file and write out the data
0638: //
0639: byte[] bytes = gclass.getBytes();
0640:
0641: FileOutputStream stream = null;
0642: try {
0643: stream = new FileOutputStream(path);
0644: stream.write(bytes, 0, bytes.length);
0645: } catch (IOException e) {
0646: getErrorCollector().addError(
0647: Message.create(e.getMessage(),
0648: CompilationUnit.this ));
0649: failures = true;
0650: } finally {
0651: if (stream != null) {
0652: try {
0653: stream.close();
0654: } catch (Exception e) {
0655: }
0656: }
0657: }
0658: }
0659: };
0660:
0661: /* checks if all needed classes are compiled before generating the bytecode */
0662: private SourceUnitOperation compileCompleteCheck = new SourceUnitOperation() {
0663: public void call(SourceUnit source)
0664: throws CompilationFailedException {
0665: List classes = source.ast.getClasses();
0666: for (Iterator it = classes.iterator(); it.hasNext();) {
0667: ClassNode node = (ClassNode) it.next();
0668: CompileUnit cu = node.getCompileUnit();
0669: for (Iterator iter = cu.iterateClassNodeToCompile(); iter
0670: .hasNext();) {
0671: String name = (String) iter.next();
0672: SourceUnit su = ast.getScriptSourceLocation(name);
0673: List classesInSourceUnit = su.ast.getClasses();
0674: StringBuffer message = new StringBuffer();
0675: message
0676: .append(
0677: "Compilation incomplete: expected to find the class ")
0678: .append(name).append(" in ").append(
0679: su.getName());
0680: if (classesInSourceUnit.size() == 0) {
0681: message
0682: .append(", but the file seems not to contain any classes");
0683: } else {
0684: message
0685: .append(", but the file contains the classes: ");
0686: boolean first = true;
0687: for (Iterator suClassesIter = classesInSourceUnit
0688: .iterator(); suClassesIter.hasNext();) {
0689: ClassNode cn = (ClassNode) suClassesIter
0690: .next();
0691: if (!first) {
0692: message.append(", ");
0693: } else {
0694: first = false;
0695: }
0696: message.append(cn.getName());
0697: }
0698: }
0699:
0700: getErrorCollector().addErrorAndContinue(
0701: new SimpleMessage(message.toString(),
0702: CompilationUnit.this ));
0703: iter.remove();
0704: }
0705: }
0706: }
0707: };
0708:
0709: /**
0710: * Runs classgen() on a single ClassNode.
0711: */
0712: private PrimaryClassNodeOperation classgen = new PrimaryClassNodeOperation() {
0713: public boolean needSortedInput() {
0714: return true;
0715: }
0716:
0717: public void call(SourceUnit source, GeneratorContext context,
0718: ClassNode classNode) throws CompilationFailedException {
0719:
0720: //
0721: // Run the Verifier on the outer class
0722: //
0723: try {
0724: verifier.visitClass(classNode);
0725: } catch (GroovyRuntimeException rpe) {
0726: ASTNode node = rpe.getNode();
0727: getErrorCollector().addError(
0728: new SyntaxException(rpe.getMessage(), null,
0729: node.getLineNumber(), node
0730: .getColumnNumber()), source);
0731: }
0732:
0733: LabelVerifier lv = new LabelVerifier(source);
0734: lv.visitClass(classNode);
0735:
0736: ClassCompletionVerifier completionVerifier = new ClassCompletionVerifier(
0737: source);
0738: completionVerifier.visitClass(classNode);
0739:
0740: // because the class may be generated even if a error was found
0741: // and that class may have an invalid format we fail here if needed
0742: getErrorCollector().failIfErrors();
0743:
0744: //
0745: // Prep the generator machinery
0746: //
0747: ClassVisitor visitor = createClassVisitor();
0748:
0749: String sourceName = (source == null ? classNode.getModule()
0750: .getDescription() : source.getName());
0751: // only show the file name and its extension like javac does in its stacktraces rather than the full path
0752: // also takes care of both \ and / depending on the host compiling environment
0753: if (sourceName != null)
0754: sourceName = sourceName.substring(Math
0755: .max(sourceName.lastIndexOf('\\'), sourceName
0756: .lastIndexOf('/')) + 1);
0757: ClassGenerator generator = new AsmClassGenerator(context,
0758: visitor, classLoader, sourceName);
0759:
0760: //
0761: // Run the generation and create the class (if required)
0762: //
0763: generator.visitClass(classNode);
0764:
0765: byte[] bytes = ((ClassWriter) visitor).toByteArray();
0766: generatedClasses.add(new GroovyClass(classNode.getName(),
0767: bytes));
0768:
0769: //
0770: // Handle any callback that's been set
0771: //
0772: if (CompilationUnit.this .classgenCallback != null) {
0773: classgenCallback.call(visitor, classNode);
0774: }
0775:
0776: //
0777: // Recurse for inner classes
0778: //
0779: LinkedList innerClasses = generator.getInnerClasses();
0780: while (!innerClasses.isEmpty()) {
0781: classgen.call(source, context, (ClassNode) innerClasses
0782: .removeFirst());
0783: }
0784: }
0785: };
0786:
0787: protected ClassVisitor createClassVisitor() {
0788: return new ClassWriter(true);
0789: }
0790:
0791: //---------------------------------------------------------------------------
0792: // PHASE HANDLING
0793:
0794: /**
0795: * Updates the phase marker on all sources.
0796: */
0797: protected void mark() throws CompilationFailedException {
0798: applyToSourceUnits(mark);
0799: }
0800:
0801: /**
0802: * Marks a single SourceUnit with the current phase,
0803: * if it isn't already there yet.
0804: */
0805: private SourceUnitOperation mark = new SourceUnitOperation() {
0806: public void call(SourceUnit source)
0807: throws CompilationFailedException {
0808: if (source.phase < phase) {
0809: source.gotoPhase(phase);
0810: }
0811:
0812: if (source.phase == phase && phaseComplete
0813: && !source.phaseComplete) {
0814: source.completePhase();
0815: }
0816: }
0817: };
0818:
0819: //---------------------------------------------------------------------------
0820: // LOOP SIMPLIFICATION FOR SourceUnit OPERATIONS
0821:
0822: /**
0823: * An callback interface for use in the applyToSourceUnits loop driver.
0824: */
0825: public static abstract class SourceUnitOperation {
0826: public abstract void call(SourceUnit source)
0827: throws CompilationFailedException;
0828: }
0829:
0830: /**
0831: * A loop driver for applying operations to all SourceUnits.
0832: * Automatically skips units that have already been processed
0833: * through the current phase.
0834: */
0835: public void applyToSourceUnits(SourceUnitOperation body)
0836: throws CompilationFailedException {
0837: Iterator keys = names.iterator();
0838: while (keys.hasNext()) {
0839: String name = (String) keys.next();
0840: SourceUnit source = (SourceUnit) sources.get(name);
0841: if ((source.phase < phase)
0842: || (source.phase == phase && !source.phaseComplete)) {
0843: try {
0844: body.call(source);
0845: } catch (CompilationFailedException e) {
0846: throw e;
0847: } catch (Exception e) {
0848: GroovyBugError gbe = new GroovyBugError(e);
0849: changeBugText(gbe, source);
0850: throw gbe;
0851: } catch (GroovyBugError e) {
0852: changeBugText(e, source);
0853: throw e;
0854: }
0855: }
0856: }
0857:
0858: getErrorCollector().failIfErrors();
0859: }
0860:
0861: //---------------------------------------------------------------------------
0862: // LOOP SIMPLIFICATION FOR PRIMARY ClassNode OPERATIONS
0863:
0864: /**
0865: * An callback interface for use in the applyToSourceUnits loop driver.
0866: */
0867: public static abstract class PrimaryClassNodeOperation {
0868: public abstract void call(SourceUnit source,
0869: GeneratorContext context, ClassNode classNode)
0870: throws CompilationFailedException;
0871:
0872: public boolean needSortedInput() {
0873: return false;
0874: }
0875: }
0876:
0877: public static abstract class GroovyClassOperation {
0878: public abstract void call(GroovyClass gclass)
0879: throws CompilationFailedException;
0880: }
0881:
0882: private List getPrimaryClassNodes(boolean sort) {
0883: ArrayList unsorted = new ArrayList();
0884: Iterator modules = this .ast.getModules().iterator();
0885: while (modules.hasNext()) {
0886: ModuleNode module = (ModuleNode) modules.next();
0887:
0888: Iterator classNodes = module.getClasses().iterator();
0889: while (classNodes.hasNext()) {
0890: ClassNode classNode = (ClassNode) classNodes.next();
0891: unsorted.add(classNode);
0892: }
0893: }
0894:
0895: if (sort == false)
0896: return unsorted;
0897:
0898: int[] indexClass = new int[unsorted.size()];
0899: int[] indexInterface = new int[unsorted.size()];
0900: {
0901: int i = 0;
0902: for (Iterator iter = unsorted.iterator(); iter.hasNext(); i++) {
0903: ClassNode node = (ClassNode) iter.next();
0904: int count = 0;
0905: ClassNode element = node;
0906: while (element != null) {
0907: count++;
0908: element = element.getSuperClass();
0909: }
0910: if (node.isInterface()) {
0911: indexInterface[i] = count;
0912: indexClass[i] = -1;
0913: } else {
0914: indexClass[i] = count;
0915: indexInterface[i] = -1;
0916: }
0917: }
0918: }
0919:
0920: List sorted = getSorted(indexInterface, unsorted);
0921: sorted.addAll(getSorted(indexClass, unsorted));
0922:
0923: return sorted;
0924: }
0925:
0926: private List getSorted(int[] index, List unsorted) {
0927: ArrayList sorted = new ArrayList(unsorted.size());
0928: int start = 0;
0929: for (int i = 0; i < unsorted.size(); i++) {
0930: int min = -1;
0931: for (int j = 0; j < unsorted.size(); j++) {
0932: if (index[j] == -1)
0933: continue;
0934: if (min == -1) {
0935: min = j;
0936: } else if (index[j] < index[min]) {
0937: min = j;
0938: }
0939: }
0940: if (min == -1)
0941: break;
0942: sorted.add(unsorted.get(min));
0943: index[min] = -1;
0944: }
0945: return sorted;
0946: }
0947:
0948: /**
0949: * A loop driver for applying operations to all primary ClassNodes in
0950: * our AST. Automatically skips units that have already been processed
0951: * through the current phase.
0952: */
0953: public void applyToPrimaryClassNodes(PrimaryClassNodeOperation body)
0954: throws CompilationFailedException {
0955: Iterator classNodes = getPrimaryClassNodes(
0956: body.needSortedInput()).iterator();
0957: while (classNodes.hasNext()) {
0958: SourceUnit context = null;
0959: try {
0960: ClassNode classNode = (ClassNode) classNodes.next();
0961: context = classNode.getModule().getContext();
0962: if (context == null || context.phase <= phase) {
0963: body.call(context, new GeneratorContext(this .ast),
0964: classNode);
0965: }
0966: } catch (CompilationFailedException e) {
0967: // fall thorugh, getErrorREporter().failIfErrors() will triger
0968: } catch (NullPointerException npe) {
0969: throw npe;
0970: } catch (GroovyBugError e) {
0971: changeBugText(e, context);
0972: throw e;
0973: } catch (Exception e) {
0974: // check the exception for a nested compilation exception
0975: ErrorCollector nestedCollector = null;
0976: for (Throwable next = e.getCause(); next != e
0977: && next != null; next = next.getCause()) {
0978: if (!(next instanceof MultipleCompilationErrorsException))
0979: continue;
0980: MultipleCompilationErrorsException mcee = (MultipleCompilationErrorsException) next;
0981: nestedCollector = mcee.collector;
0982: break;
0983: }
0984:
0985: if (nestedCollector != null) {
0986: getErrorCollector().addCollectorContents(
0987: nestedCollector);
0988: } else {
0989: getErrorCollector().addError(
0990: new ExceptionMessage(e, configuration
0991: .getDebug(), this ));
0992: }
0993: }
0994: }
0995:
0996: getErrorCollector().failIfErrors();
0997: }
0998:
0999: public void applyToGeneratedGroovyClasses(GroovyClassOperation body)
1000: throws CompilationFailedException {
1001: if (this .phase != Phases.OUTPUT
1002: && !(this .phase == Phases.CLASS_GENERATION && this .phaseComplete)) {
1003: throw new GroovyBugError(
1004: "CompilationUnit not ready for output(). Current phase="
1005: + getPhaseDescription());
1006: }
1007:
1008: boolean failures = false;
1009:
1010: Iterator iterator = this .generatedClasses.iterator();
1011: while (iterator.hasNext()) {
1012: //
1013: // Get the class and calculate its filesystem name
1014: //
1015: GroovyClass gclass = (GroovyClass) iterator.next();
1016: try {
1017: body.call(gclass);
1018: } catch (CompilationFailedException e) {
1019: // fall thorugh, getErrorREporter().failIfErrors() will triger
1020: } catch (NullPointerException npe) {
1021: throw npe;
1022: } catch (GroovyBugError e) {
1023: changeBugText(e, null);
1024: throw e;
1025: } catch (Exception e) {
1026: GroovyBugError gbe = new GroovyBugError(e);
1027: throw gbe;
1028: }
1029: }
1030:
1031: getErrorCollector().failIfErrors();
1032: }
1033:
1034: private void changeBugText(GroovyBugError e, SourceUnit context) {
1035: e.setBugText("exception in phase '" + getPhaseDescription()
1036: + "' in source unit '"
1037: + ((context != null) ? context.getName() : "?") + "' "
1038: + e.getBugText());
1039: }
1040: }
|