blob: 0e69aa729354ab6e76cf1f0c339cdc283f645440 [file] [log] [blame]
/*
$Id$
Copyright 2003 (C) James Strachan and Bob Mcwhirter. All Rights Reserved.
Redistribution and use of this software and associated documentation
("Software"), with or without modification, are permitted provided
that the following conditions are met:
1. Redistributions of source code must retain copyright
statements and notices. Redistributions must also contain a
copy of this document.
2. Redistributions in binary form must reproduce the
above copyright notice, this list of conditions and the
following disclaimer in the documentation and/or other
materials provided with the distribution.
3. The name "groovy" must not be used to endorse or promote
products derived from this Software without prior written
permission of The Codehaus. For written permission,
please contact info@codehaus.org.
4. Products derived from this Software may not be called "groovy"
nor may "groovy" appear in their names without prior written
permission of The Codehaus. "groovy" is a registered
trademark of The Codehaus.
5. Due credit should be given to The Codehaus -
http://groovy.codehaus.org/
THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.codehaus.groovy.control;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.CodeSource;
import java.util.*;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.CompileUnit;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.classgen.AsmClassGenerator;
import org.codehaus.groovy.classgen.ClassCompletionVerifier;
import org.codehaus.groovy.classgen.ClassGenerator;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.classgen.VariableScopeVisitor;
import org.codehaus.groovy.classgen.Verifier;
import org.codehaus.groovy.control.io.InputStreamReaderSource;
import org.codehaus.groovy.control.io.ReaderSource;
import org.codehaus.groovy.control.messages.ExceptionMessage;
import org.codehaus.groovy.control.messages.Message;
import org.codehaus.groovy.control.messages.SimpleMessage;
import org.codehaus.groovy.syntax.SyntaxException;
import org.codehaus.groovy.syntax.ClassSource;
import org.codehaus.groovy.syntax.SourceSummary;
import org.codehaus.groovy.tools.GroovyClass;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyRuntimeException;
/**
* Collects all compilation data as it is generated by the compiler system.
* Allows additional source units to be added and compilation run again (to
* affect only the deltas).
*
* @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a>
* @author <a href="mailto:blackdrag@gmx.org">Jochen Theodorou</a>
* @version $Id$
*/
public class CompilationUnit extends ProcessingUnit {
//---------------------------------------------------------------------------
// CONSTRUCTION AND SUCH
protected HashMap sources; // The SourceUnits from which this unit is built
protected Map summariesBySourceName; // Summary of each SourceUnit
protected Map summariesByPublicClassName; // Summary of each SourceUnit
protected Map classSourcesByPublicClassName; // Summary of each Class
protected ArrayList names; // Names for each SourceUnit in sources.
protected LinkedList queuedSources;
protected CompileUnit ast; // The overall AST for this CompilationUnit.
protected ArrayList generatedClasses; // The classes generated during classgen.
protected Verifier verifier; // For use by verify().
protected boolean debug; // Controls behaviour of classgen() and other routines.
protected boolean configured; // Set true after the first configure() operation
protected ClassgenCallback classgenCallback; // A callback for use during classgen()
protected ProgressCallback progressCallback; // A callback for use during compile()
protected ResolveVisitor resolveVisitor;
LinkedList[] phaseOperations;
/**
* Initializes the CompilationUnit with defaults.
*/
public CompilationUnit() {
this(null, null, null);
}
/**
* Initializes the CompilationUnit with defaults except for class loader.
*/
public CompilationUnit(GroovyClassLoader loader) {
this(null, null, loader);
}
/**
* Initializes the CompilationUnit with no security considerations.
*/
public CompilationUnit(CompilerConfiguration configuration) {
this(configuration, null, null);
}
/**
* Initializes the CompilationUnit with a CodeSource for controlling
* security stuff and a class loader for loading classes.
*/
public CompilationUnit(CompilerConfiguration configuration, CodeSource security, GroovyClassLoader loader) {
super(configuration, loader, null);
this.names = new ArrayList();
this.queuedSources = new LinkedList();
this.sources = new HashMap();
this.summariesBySourceName = new HashMap();
this.summariesByPublicClassName = new HashMap();
this.classSourcesByPublicClassName = new HashMap();
this.ast = new CompileUnit(this.classLoader, security, this.configuration);
this.generatedClasses = new ArrayList();
this.verifier = new Verifier();
this.resolveVisitor = new ResolveVisitor(this);
phaseOperations = new LinkedList[Phases.ALL+1];
for (int i=0; i<phaseOperations.length; i++) {
phaseOperations[i] = new LinkedList();
}
addPhaseOperation(new SourceUnitOperation() {
public void call(SourceUnit source) throws CompilationFailedException {
source.parse();
}
}, Phases.PARSING);
addPhaseOperation(summarize, Phases.PARSING);
addPhaseOperation(convert, Phases.CONVERSION);
addPhaseOperation(resolve, Phases.SEMANTIC_ANALYSIS);
addPhaseOperation(compileCompleteCheck, Phases.CANONICALIZATION);
addPhaseOperation(classgen, Phases.CLASS_GENERATION);
addPhaseOperation(output);
this.classgenCallback = null;
}
public void addPhaseOperation(SourceUnitOperation op, int phase) {
if (phase<0 || phase>Phases.ALL) throw new IllegalArgumentException("phase "+phase+" is unknown");
phaseOperations[phase].add(op);
}
public void addPhaseOperation(PrimaryClassNodeOperation op, int phase) {
if (phase<0 || phase>Phases.ALL) throw new IllegalArgumentException("phase "+phase+" is unknown");
phaseOperations[phase].add(op);
}
public void addPhaseOperation(GroovyClassOperation op) {
phaseOperations[Phases.OUTPUT].addFirst(op);
}
/**
* Configures its debugging mode and classloader classpath from a given compiler configuration.
* This cannot be done more than once due to limitations in {@link java.net.URLClassLoader URLClassLoader}.
*/
public void configure(CompilerConfiguration configuration) {
super.configure(configuration);
this.debug = configuration.getDebug();
if (!this.configured && this.classLoader instanceof GroovyClassLoader) {
appendCompilerConfigurationClasspathToClassLoader(configuration, (GroovyClassLoader) this.classLoader);
}
this.configured = true;
}
private void appendCompilerConfigurationClasspathToClassLoader(CompilerConfiguration configuration, GroovyClassLoader classLoader) {
/*for (Iterator iterator = configuration.getClasspath().iterator(); iterator.hasNext(); ) {
classLoader.addClasspath((String) iterator.next());
}*/
}
/**
* Returns the CompileUnit that roots our AST.
*/
public CompileUnit getAST() {
return this.ast;
}
/**
* Get the source summaries
*/
public Map getSummariesBySourceName() {
return summariesBySourceName;
}
public Map getSummariesByPublicClassName() {
return summariesByPublicClassName;
}
public Map getClassSourcesByPublicClassName() {
return classSourcesByPublicClassName;
}
public boolean isPublicClass(String className) {
return summariesByPublicClassName.containsKey(className);
}
/**
* Get the GroovyClasses generated by compile().
*/
public List getClasses() {
return generatedClasses;
}
/**
* Convenience routine to get the first ClassNode, for
* when you are sure there is only one.
*/
public ClassNode getFirstClassNode() {
return (ClassNode) ((ModuleNode) this.ast.getModules().get(0)).getClasses().get(0);
}
/**
* Convenience routine to get the named ClassNode.
*/
public ClassNode getClassNode(final String name) {
final ClassNode[] result = new ClassNode[]{null};
PrimaryClassNodeOperation handler = new PrimaryClassNodeOperation() {
public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) {
if (classNode.getName().equals(name)) {
result[0] = classNode;
}
}
};
try {
applyToPrimaryClassNodes(handler);
} catch (CompilationFailedException e) {
if (debug) e.printStackTrace();
}
return result[0];
}
//---------------------------------------------------------------------------
// SOURCE CREATION
/**
* Adds a set of file paths to the unit.
*/
public void addSources(String[] paths) {
for (int i = 0; i < paths.length; i++) {
File file = new File(paths[i]);
addSource(file);
}
}
/**
* Adds a set of source files to the unit.
*/
public void addSources(File[] files) {
for (int i = 0; i < files.length; i++) {
addSource(files[i]);
}
}
/**
* Adds a source file to the unit.
*/
public SourceUnit addSource(File file) {
return addSource(new SourceUnit(file, configuration, classLoader, getErrorCollector()));
}
/**
* Adds a source file to the unit.
*/
public SourceUnit addSource(URL url) {
return addSource(new SourceUnit(url, configuration, classLoader,getErrorCollector()));
}
/**
* Adds a InputStream source to the unit.
*/
public SourceUnit addSource(String name, InputStream stream) {
ReaderSource source = new InputStreamReaderSource(stream, configuration);
return addSource(new SourceUnit(name, source, configuration, classLoader, getErrorCollector()));
}
/**
* Adds a SourceUnit to the unit.
*/
public SourceUnit addSource(SourceUnit source) {
String name = source.getName();
source.setClassLoader(this.classLoader);
for (Iterator iter = queuedSources.iterator(); iter.hasNext();) {
SourceUnit su = (SourceUnit) iter.next();
if (name.equals(su.getName())) return su;
}
queuedSources.add(source);
return source;
}
/**
* Returns an iterator on the unit's SourceUnits.
*/
public Iterator iterator() {
return new Iterator() {
Iterator nameIterator = names.iterator();
public boolean hasNext() {
return nameIterator.hasNext();
}
public Object next() {
String name = (String) nameIterator.next();
return sources.get(name);
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
/**
* Adds a ClassNode directly to the unit (ie. without source).
* WARNING: the source is needed for error reporting, using
* this method without setting a SourceUnit will cause
* NullPinterExceptions
*/
public void addClassNode(ClassNode node) {
ModuleNode module = new ModuleNode(this.ast);
this.ast.addModule(module);
module.addClass(node);
}
//---------------------------------------------------------------------------
// EXTERNAL CALLBACKS
/**
* A callback interface you can use to "accompany" the classgen()
* code as it traverses the ClassNode tree. You will be called-back
* for each primary and inner class. Use setClassgenCallback() before
* running compile() to set your callback.
*/
public static abstract class ClassgenCallback {
public abstract void call(ClassVisitor writer, ClassNode node) throws CompilationFailedException;
}
/**
* Sets a ClassgenCallback. You can have only one, and setting
* it to null removes any existing setting.
*/
public void setClassgenCallback(ClassgenCallback visitor) {
this.classgenCallback = visitor;
}
/**
* A callback interface you can use to get a callback after every
* unit of the compile process. You will be called-back with a
* ProcessingUnit and a phase indicator. Use setProgressCallback()
* before running compile() to set your callback.
*/
public static abstract class ProgressCallback {
public abstract void call(ProcessingUnit context, int phase) throws CompilationFailedException;
}
/**
* Sets a ProgressCallback. You can have only one, and setting
* it to null removes any existing setting.
*/
public void setProgressCallback(ProgressCallback callback) {
this.progressCallback = callback;
}
//---------------------------------------------------------------------------
// ACTIONS
/**
* Synonym for compile(Phases.ALL).
*/
public void compile() throws CompilationFailedException {
compile(Phases.ALL);
}
/**
* Compiles the compilation unit from sources.
*/
public void compile(int throughPhase) throws CompilationFailedException {
//
// To support delta compilations, we always restart
// the compiler. The individual passes are responsible
// for not reprocessing old code.
gotoPhase(Phases.INITIALIZATION);
throughPhase = Math.min(throughPhase,Phases.ALL);
while (throughPhase >= phase && phase <= Phases.ALL) {
for (Iterator it = phaseOperations[phase].iterator(); it.hasNext();) {
Object operation = it.next();
if (operation instanceof PrimaryClassNodeOperation) {
applyToPrimaryClassNodes((PrimaryClassNodeOperation) operation);
} else if (operation instanceof SourceUnitOperation) {
applyToSourceUnits((SourceUnitOperation)operation);
} else {
applyToGeneratedGroovyClasses((GroovyClassOperation)operation);
}
}
if (dequeued()) continue;
if (progressCallback != null) progressCallback.call(this, phase);
completePhase();
applyToSourceUnits(mark);
gotoPhase(phase+1);
if (phase==Phases.CLASS_GENERATION) {
sortClasses();
}
}
errorCollector.failIfErrors();
}
private void sortClasses() throws CompilationFailedException {
Iterator modules = this.ast.getModules().iterator();
while (modules.hasNext()) {
ModuleNode module = (ModuleNode) modules.next();
// before we actually do the sorting we should check
// for cyclic references
List classes = module.getClasses();
for (Iterator iter = classes.iterator(); iter.hasNext();) {
ClassNode start = (ClassNode) iter.next();
ClassNode cn = start;
HashSet parents = new HashSet();
do {
if (parents.contains(cn.getName())) {
getErrorCollector().addErrorAndContinue(
new SimpleMessage("cyclic inheritance involving "+cn.getName()+" in class "+start.getName(),this)
);
cn=null;
} else {
parents.add(cn.getName());
cn = cn.getSuperClass();
}
} while (cn!=null);
}
errorCollector.failIfErrors();
module.sortClasses();
}
}
/**
* Dequeues any source units add through addSource and resets the compiler phase
* to initialization.
*
* Note: this does not mean a file is recompiled. If a SoucreUnit has already passed
* a phase it is skipped until a higher phase is reached.
* @return TODO
*
* @throws CompilationFailedException
*/
protected boolean dequeued() throws CompilationFailedException {
boolean dequeue = !queuedSources.isEmpty();
while (!queuedSources.isEmpty()) {
SourceUnit su = (SourceUnit) queuedSources.removeFirst();
String name = su.getName();
names.add(name);
sources.put(name,su);
}
if (dequeue) {
gotoPhase(Phases.INITIALIZATION);
}
return dequeue;
}
/**
* Adds summary of each class to maps
*/
private SourceUnitOperation summarize = new SourceUnitOperation() {
public void call(SourceUnit source) throws CompilationFailedException {
SourceSummary sourceSummary = source.getSourceSummary();
if (sourceSummary != null) {
summariesBySourceName.put(source.getName(),sourceSummary);
List publicClassSources = sourceSummary.getPublicClassSources();
if (publicClassSources == null || publicClassSources.size() == 0) {
//todo - is this the best way to handle scripts?
summariesByPublicClassName.put("*NoName*",sourceSummary);
// nothing to put into classSourcesByClassName as no ClassSource
} else {
Iterator itr = publicClassSources.iterator();
while (itr.hasNext()) {
ClassSource classSource = (ClassSource)itr.next();
summariesByPublicClassName.put(classSource.getName(),sourceSummary);
classSourcesByPublicClassName.put(classSource.getName(),classSource);
}
}
}
}
};
/**
* Resolves all types
*/
private SourceUnitOperation resolve = new SourceUnitOperation() {
public void call(SourceUnit source) throws CompilationFailedException {
List classes = source.ast.getClasses();
for (Iterator it = classes.iterator(); it.hasNext();) {
ClassNode node = (ClassNode) it.next();
VariableScopeVisitor scopeVisitor = new VariableScopeVisitor(source);
scopeVisitor.visitClass(node);
resolveVisitor.startResolving(node,source);
}
}
};
/**
* Runs convert() on a single SourceUnit.
*/
private SourceUnitOperation convert = new SourceUnitOperation() {
public void call(SourceUnit source) throws CompilationFailedException {
source.convert();
CompilationUnit.this.ast.addModule(source.getAST());
if (CompilationUnit.this.progressCallback != null) {
CompilationUnit.this.progressCallback.call(source, CompilationUnit.this.phase);
}
}
};
private GroovyClassOperation output = new GroovyClassOperation() {
public void call(GroovyClass gclass) throws CompilationFailedException {
boolean failures = false;
String name = gclass.getName().replace('.', File.separatorChar) + ".class";
File path = new File(configuration.getTargetDirectory(), name);
//
// Ensure the path is ready for the file
//
File directory = path.getParentFile();
if (directory != null && !directory.exists()) {
directory.mkdirs();
}
//
// Create the file and write out the data
//
byte[] bytes = gclass.getBytes();
FileOutputStream stream = null;
try {
stream = new FileOutputStream(path);
stream.write(bytes, 0, bytes.length);
} catch (IOException e) {
getErrorCollector().addError(Message.create(e.getMessage(),CompilationUnit.this));
failures = true;
} finally {
if (stream != null) {
try {
stream.close();
} catch (Exception e) {
}
}
}
}
};
/* checks if all needed classes are compiled before generating the bytecode */
private SourceUnitOperation compileCompleteCheck = new SourceUnitOperation() {
public void call(SourceUnit source) throws CompilationFailedException {
List classes = source.ast.getClasses();
for (Iterator it = classes.iterator(); it.hasNext();) {
ClassNode node = (ClassNode) it.next();
CompileUnit cu = node.getCompileUnit();
for (Iterator iter = cu.iterateClassNodeToCompile(); iter.hasNext();) {
String name = (String) iter.next();
SourceUnit su = ast.getScriptSourceLocation(name);
List classesInSourceUnit = su.ast.getClasses();
StringBuffer message = new StringBuffer();
message
.append ("Compilation incomplete: expected to find the class ")
.append (name)
.append (" in ")
.append (su.getName());
if (classesInSourceUnit.size()==0) {
message.append(", but the file seems not to contain any classes");
} else {
message.append(", but the file contains the classes: ");
boolean first = true;
for (Iterator suClassesIter = classesInSourceUnit
.iterator(); suClassesIter.hasNext();) {
ClassNode cn = (ClassNode) suClassesIter.next();
if (!first) {
message.append(", ");
} else {
first=false;
}
message.append(cn.getName());
}
}
getErrorCollector().addErrorAndContinue(
new SimpleMessage(message.toString(),CompilationUnit.this)
);
iter.remove();
}
}
}
};
/**
* Runs classgen() on a single ClassNode.
*/
private PrimaryClassNodeOperation classgen = new PrimaryClassNodeOperation() {
public boolean needSortedInput() {
return true;
}
public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException {
//
// Run the Verifier on the outer class
//
try {
verifier.visitClass(classNode);
} catch (GroovyRuntimeException rpe) {
ASTNode node = rpe.getNode();
getErrorCollector().addError(
new SyntaxException(rpe.getMessage(),null,node.getLineNumber(),node.getColumnNumber()),
source
);
}
LabelVerifier lv = new LabelVerifier(source);
lv.visitClass(classNode);
ClassCompletionVerifier completionVerifier = new ClassCompletionVerifier(source);
completionVerifier.visitClass(classNode);
// because the class may be generated even if a error was found
// and that class may have an invalid format we fail here if needed
getErrorCollector().failIfErrors();
//
// Prep the generator machinery
//
ClassVisitor visitor = createClassVisitor();
String sourceName = (source == null ? classNode.getModule().getDescription() : source.getName());
// only show the file name and its extension like javac does in its stacktraces rather than the full path
// also takes care of both \ and / depending on the host compiling environment
if (sourceName != null)
sourceName = sourceName.substring(Math.max(sourceName.lastIndexOf('\\'), sourceName.lastIndexOf('/')) + 1);
ClassGenerator generator = new AsmClassGenerator(context, visitor, classLoader, sourceName);
//
// Run the generation and create the class (if required)
//
generator.visitClass(classNode);
byte[] bytes = ((ClassWriter) visitor).toByteArray();
generatedClasses.add(new GroovyClass(classNode.getName(), bytes));
//
// Handle any callback that's been set
//
if (CompilationUnit.this.classgenCallback != null) {
classgenCallback.call(visitor, classNode);
}
//
// Recurse for inner classes
//
LinkedList innerClasses = generator.getInnerClasses();
while (!innerClasses.isEmpty()) {
classgen.call(source, context, (ClassNode) innerClasses.removeFirst());
}
}
};
protected ClassVisitor createClassVisitor() {
return new ClassWriter(true);
}
//---------------------------------------------------------------------------
// PHASE HANDLING
/**
* Updates the phase marker on all sources.
*/
protected void mark() throws CompilationFailedException {
applyToSourceUnits(mark);
}
/**
* Marks a single SourceUnit with the current phase,
* if it isn't already there yet.
*/
private SourceUnitOperation mark = new SourceUnitOperation() {
public void call(SourceUnit source) throws CompilationFailedException {
if (source.phase < phase) {
source.gotoPhase(phase);
}
if (source.phase == phase && phaseComplete && !source.phaseComplete) {
source.completePhase();
}
}
};
//---------------------------------------------------------------------------
// LOOP SIMPLIFICATION FOR SourceUnit OPERATIONS
/**
* An callback interface for use in the applyToSourceUnits loop driver.
*/
public static abstract class SourceUnitOperation {
public abstract void call(SourceUnit source) throws CompilationFailedException;
}
/**
* A loop driver for applying operations to all SourceUnits.
* Automatically skips units that have already been processed
* through the current phase.
*/
public void applyToSourceUnits(SourceUnitOperation body) throws CompilationFailedException {
Iterator keys = names.iterator();
while (keys.hasNext()) {
String name = (String) keys.next();
SourceUnit source = (SourceUnit) sources.get(name);
if ( (source.phase < phase) || (source.phase == phase && !source.phaseComplete)) {
try {
body.call(source);
} catch (CompilationFailedException e) {
throw e;
} catch (Exception e) {
GroovyBugError gbe = new GroovyBugError(e);
changeBugText(gbe,source);
throw gbe;
} catch (GroovyBugError e) {
changeBugText(e,source);
throw e;
}
}
}
getErrorCollector().failIfErrors();
}
//---------------------------------------------------------------------------
// LOOP SIMPLIFICATION FOR PRIMARY ClassNode OPERATIONS
/**
* An callback interface for use in the applyToSourceUnits loop driver.
*/
public static abstract class PrimaryClassNodeOperation {
public abstract void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException;
public boolean needSortedInput(){
return false;
}
}
public static abstract class GroovyClassOperation {
public abstract void call(GroovyClass gclass) throws CompilationFailedException;
}
private List getPrimaryClassNodes(boolean sort) {
ArrayList unsorted = new ArrayList();
Iterator modules = this.ast.getModules().iterator();
while (modules.hasNext()) {
ModuleNode module = (ModuleNode) modules.next();
Iterator classNodes = module.getClasses().iterator();
while (classNodes.hasNext()) {
ClassNode classNode = (ClassNode) classNodes.next();
unsorted.add(classNode);
}
}
if(sort==false) return unsorted;
int[] indexClass = new int[unsorted.size()];
int[] indexInterface = new int[unsorted.size()];
{
int i = 0;
for (Iterator iter = unsorted.iterator(); iter.hasNext(); i++) {
ClassNode node = (ClassNode) iter.next();
int count = 0;
ClassNode element = node;
while (element!=null){
count++;
element = element.getSuperClass();
}
if (node.isInterface()) {
indexInterface[i] = count;
indexClass[i] = -1;
} else {
indexClass[i] = count;
indexInterface[i] = -1;
}
}
}
List sorted = getSorted(indexInterface,unsorted);
sorted.addAll(getSorted(indexClass,unsorted));
return sorted;
}
private List getSorted(int[] index, List unsorted) {
ArrayList sorted = new ArrayList(unsorted.size());
int start = 0;
for (int i=0; i<unsorted.size(); i++) {
int min = -1;
for (int j=0; j<unsorted.size(); j++) {
if (index[j]==-1) continue;
if (min==-1) {
min = j;
} else if (index[j]<index[min]) {
min = j;
}
}
if (min==-1) break;
sorted.add(unsorted.get(min));
index[min] = -1;
}
return sorted;
}
/**
* A loop driver for applying operations to all primary ClassNodes in
* our AST. Automatically skips units that have already been processed
* through the current phase.
*/
public void applyToPrimaryClassNodes(PrimaryClassNodeOperation body) throws CompilationFailedException {
Iterator classNodes = getPrimaryClassNodes(body.needSortedInput()).iterator();
while (classNodes.hasNext()) {
SourceUnit context=null;
try {
ClassNode classNode = (ClassNode) classNodes.next();
context = classNode.getModule().getContext();
if (context == null || context.phase <= phase) {
body.call(context, new GeneratorContext(this.ast), classNode);
}
} catch (CompilationFailedException e) {
// fall thorugh, getErrorREporter().failIfErrors() will triger
} catch (NullPointerException npe){
throw npe;
} catch (GroovyBugError e) {
changeBugText(e,context);
throw e;
} catch (Exception e) {
// check the exception for a nested compilation exception
ErrorCollector nestedCollector = null;
for (Throwable next = e.getCause(); next!=e && next!=null; next=next.getCause()) {
if (!(next instanceof MultipleCompilationErrorsException)) continue;
MultipleCompilationErrorsException mcee = (MultipleCompilationErrorsException) next;
nestedCollector = mcee.collector;
break;
}
if (nestedCollector!=null) {
getErrorCollector().addCollectorContents(nestedCollector);
} else {
getErrorCollector().addError(new ExceptionMessage(e,configuration.getDebug(),this));
}
}
}
getErrorCollector().failIfErrors();
}
public void applyToGeneratedGroovyClasses(GroovyClassOperation body) throws CompilationFailedException {
if (this.phase != Phases.OUTPUT && !(this.phase == Phases.CLASS_GENERATION && this.phaseComplete)) {
throw new GroovyBugError("CompilationUnit not ready for output(). Current phase="+getPhaseDescription());
}
boolean failures = false;
Iterator iterator = this.generatedClasses.iterator();
while (iterator.hasNext()) {
//
// Get the class and calculate its filesystem name
//
GroovyClass gclass = (GroovyClass) iterator.next();
try {
body.call(gclass);
} catch (CompilationFailedException e) {
// fall thorugh, getErrorREporter().failIfErrors() will triger
} catch (NullPointerException npe){
throw npe;
} catch (GroovyBugError e) {
changeBugText(e,null);
throw e;
} catch (Exception e) {
GroovyBugError gbe = new GroovyBugError(e);
throw gbe;
}
}
getErrorCollector().failIfErrors();
}
private void changeBugText(GroovyBugError e, SourceUnit context) {
e.setBugText("exception in phase '"+getPhaseDescription()+"' in source unit '"+((context!=null)?context.getName():"?")+"' "+e.getBugText());
}
}