/*
* Copyright 2003-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.groovy.control;
import groovy.lang.GroovyClassLoader;
import groovy.lang.GroovyRuntimeException;
import groovy.transform.CompilationUnitAware;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.classgen.*;
import org.codehaus.groovy.control.customizers.CompilationCustomizer;
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.tools.GroovyClass;
import org.codehaus.groovy.transform.ASTTransformationVisitor;
import org.codehaus.groovy.transform.AnnotationCollectorTransform;
import org.codehaus.groovy.transform.trait.TraitComposer;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
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.*;
/**
* The CompilationUnit collects all compilation data as it is generated by the compiler system.
* You can use this object to add additional source units to the compilation, or force the
* compilation to be run again (to affect only the deltas).
* <p>
* You can also add PhaseOperations to this compilation using the addPhaseOperation method.
* This is commonly used when you want to wire a new AST Transformation into the compilation.
*
* @author <a href="mailto:cpoirier@dreaming.org">Chris Poirier</a>
* @author <a href="mailto:blackdrag@gmx.org">Jochen Theodorou</a>
* @author <a href="mailto:roshandawrani@codehaus.org">Roshan Dawrani</a>
*/
public class CompilationUnit extends ProcessingUnit {
//---------------------------------------------------------------------------
// CONSTRUCTION AND SUCH
protected ASTTransformationsContext astTransformationsContext; // AST transformations state data
protected Map<String, SourceUnit> 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 List<String> names; // Names for each SourceUnit in sources.
protected LinkedList<SourceUnit> queuedSources;
protected CompileUnit ast; // The overall AST for this CompilationUnit.
protected List<GroovyClass> generatedClasses; // The classes generated during classgen.
protected Verifier verifier; // For use by verify().
protected boolean debug; // Controls behavior 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;
protected StaticImportVisitor staticImportVisitor;
protected OptimizerVisitor optimizer;
protected ClassNodeResolver classNodeResolver;
LinkedList[] phaseOperations;
LinkedList[] newPhaseOperations;
/**
* 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) {
this(configuration, security, loader, null);
}
/**
* Initializes the CompilationUnit with a CodeSource for controlling
* security stuff, a class loader for loading classes, and a class
* loader for loading AST transformations.
* <b>Note</b> The transform loader must be
* able to load compiler classes. That means CompilationUnit.class.classLoader
* must be at last a parent to transformLoader. The other loader has no such constraint.
*
* @param transformLoader - the loader for transforms
* @param loader - loader used to resolve classes against during compilation
* @param security - security setting for the compilation
* @param configuration - compilation configuration
*/
public CompilationUnit(CompilerConfiguration configuration, CodeSource security,
GroovyClassLoader loader, GroovyClassLoader transformLoader) {
super(configuration, loader, null);
this.astTransformationsContext = new ASTTransformationsContext(this, transformLoader);
this.names = new ArrayList<String>();
this.queuedSources = new LinkedList<SourceUnit>();
this.sources = new HashMap<String, SourceUnit>();
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<GroovyClass>();
this.verifier = new Verifier();
this.resolveVisitor = new ResolveVisitor(this);
this.staticImportVisitor = new StaticImportVisitor();
this.optimizer = new OptimizerVisitor(this);
phaseOperations = new LinkedList[Phases.ALL + 1];
newPhaseOperations = new LinkedList[Phases.ALL + 1];
for (int i = 0; i < phaseOperations.length; i++) {
phaseOperations[i] = new LinkedList();
newPhaseOperations[i] = new LinkedList();
}
addPhaseOperation(new SourceUnitOperation() {
public void call(SourceUnit source) throws CompilationFailedException {
source.parse();
}
}, Phases.PARSING);
addPhaseOperation(convert, Phases.CONVERSION);
addPhaseOperation(new PrimaryClassNodeOperation() {
public void call(SourceUnit source, GeneratorContext context,
ClassNode classNode) throws CompilationFailedException {
EnumVisitor ev = new EnumVisitor(CompilationUnit.this, source);
ev.visitClass(classNode);
}
}, Phases.CONVERSION);
addPhaseOperation(resolve, Phases.SEMANTIC_ANALYSIS);
addPhaseOperation(staticImport, Phases.SEMANTIC_ANALYSIS);
addPhaseOperation(new PrimaryClassNodeOperation() {
@Override
public void call(SourceUnit source, GeneratorContext context,
ClassNode classNode) throws CompilationFailedException {
InnerClassVisitor iv = new InnerClassVisitor(CompilationUnit.this, source);
iv.visitClass(classNode);
}
}, Phases.SEMANTIC_ANALYSIS);
addPhaseOperation(new PrimaryClassNodeOperation() {
public void call(SourceUnit source, GeneratorContext context,
ClassNode classNode) throws CompilationFailedException {
TraitComposer.doExtendTraits(classNode, source, CompilationUnit.this);
}
}, Phases.CANONICALIZATION);
addPhaseOperation(compileCompleteCheck, Phases.CANONICALIZATION);
addPhaseOperation(classgen, Phases.CLASS_GENERATION);
addPhaseOperation(output);
addPhaseOperation(new PrimaryClassNodeOperation() {
@Override
public void call(SourceUnit source, GeneratorContext context,
ClassNode classNode) throws CompilationFailedException {
AnnotationCollectorTransform.ClassChanger actt = new AnnotationCollectorTransform.ClassChanger();
actt.transformClass(classNode);
}
}, Phases.SEMANTIC_ANALYSIS);
ASTTransformationVisitor.addPhaseOperations(this);
addPhaseOperation(new PrimaryClassNodeOperation() {
@Override
public void call(SourceUnit source, GeneratorContext context,
ClassNode classNode) throws CompilationFailedException {
StaticVerifier sv = new StaticVerifier();
sv.visitClass(classNode, source);
}
}, Phases.SEMANTIC_ANALYSIS);
addPhaseOperation(new PrimaryClassNodeOperation() {
@Override
public void call(SourceUnit source, GeneratorContext context,
ClassNode classNode) throws CompilationFailedException {
InnerClassCompletionVisitor iv = new InnerClassCompletionVisitor(CompilationUnit.this, source);
iv.visitClass(classNode);
}
}, Phases.CANONICALIZATION);
addPhaseOperation(new PrimaryClassNodeOperation() {
public void call(SourceUnit source, GeneratorContext context,
ClassNode classNode) throws CompilationFailedException {
EnumCompletionVisitor ecv = new EnumCompletionVisitor(CompilationUnit.this, source);
ecv.visitClass(classNode);
}
}, Phases.CANONICALIZATION);
// apply configuration customizers if any
if (configuration != null) {
final List<CompilationCustomizer> customizers = configuration.getCompilationCustomizers();
for (CompilationCustomizer customizer : customizers) {
if (customizer instanceof CompilationUnitAware) {
((CompilationUnitAware) customizer).setCompilationUnit(this);
}
addPhaseOperation(customizer, customizer.getPhase().getPhaseNumber());
}
}
this.classgenCallback = null;
this.classNodeResolver = new ClassNodeResolver();
}
/**
* Returns the class loader for loading AST transformations.
* @return - the transform class loader
*/
public GroovyClassLoader getTransformLoader() {
return astTransformationsContext.getTransformLoader() == null ? getClassLoader() : astTransformationsContext.getTransformLoader();
}
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 addFirstPhaseOperation(PrimaryClassNodeOperation op, int phase) {
if (phase < 0 || phase > Phases.ALL) throw new IllegalArgumentException("phase " + phase + " is unknown");
phaseOperations[phase].add(0, op);
}
public void addPhaseOperation(GroovyClassOperation op) {
phaseOperations[Phases.OUTPUT].addFirst(op);
}
public void addNewPhaseOperation(SourceUnitOperation op, int phase) {
if (phase < 0 || phase > Phases.ALL) throw new IllegalArgumentException("phase " + phase + " is unknown");
newPhaseOperations[phase].add(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 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];
}
/**
* @return the AST transformations current context
*/
public ASTTransformationsContext getASTTransformationsContext() {
return astTransformationsContext;
}
//---------------------------------------------------------------------------
// SOURCE CREATION
/**
* Adds a set of file paths to the unit.
*/
public void addSources(String[] paths) {
for (String path : paths) {
addSource(new File(path));
}
}
/**
* Adds a set of source files to the unit.
*/
public void addSources(File[] files) {
for (File file : files) {
addSource(file);
}
}
/**
* 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()));
}
public SourceUnit addSource(String name, String scriptText) {
return addSource(new SourceUnit(name, scriptText, configuration, classLoader, getErrorCollector()));
}
/**
* Adds a SourceUnit to the unit.
*/
public SourceUnit addSource(SourceUnit source) {
String name = source.getName();
source.setClassLoader(this.classLoader);
for (SourceUnit su : queuedSources) {
if (name.equals(su.getName())) return su;
}
queuedSources.add(source);
return source;
}
/**
* Returns an iterator on the unit's SourceUnits.
*/
public Iterator<SourceUnit> iterator() {
return new Iterator<SourceUnit>() {
Iterator<String> nameIterator = names.iterator();
public boolean hasNext() {
return nameIterator.hasNext();
}
public SourceUnit next() {
String name = 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 abstract static 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 abstract static 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;
}
public ClassgenCallback getClassgenCallback() {
return classgenCallback;
}
public ProgressCallback getProgressCallback() {
return progressCallback;
}
//---------------------------------------------------------------------------
// 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) {
if (phase == Phases.SEMANTIC_ANALYSIS) {
doPhaseOperation(resolve);
if (dequeued()) continue;
}
processPhaseOperations(phase);
// Grab processing may have brought in new AST transforms into various phases, process them as well
processNewPhaseOperations(phase);
if (progressCallback != null) progressCallback.call(this, phase);
completePhase();
applyToSourceUnits(mark);
if (dequeued()) continue;
gotoPhase(phase + 1);
if (phase == Phases.CLASS_GENERATION) {
sortClasses();
}
}
errorCollector.failIfErrors();
}
private void processPhaseOperations(int ph) {
LinkedList ops = phaseOperations[ph];
for (Object next : ops) {
doPhaseOperation(next);
}
}
private void processNewPhaseOperations(int currPhase) {
recordPhaseOpsInAllOtherPhases(currPhase);
LinkedList currentPhaseNewOps = newPhaseOperations[currPhase];
while (!currentPhaseNewOps.isEmpty()) {
Object operation = currentPhaseNewOps.removeFirst();
// push this operation to master list and then process it.
phaseOperations[currPhase].add(operation);
doPhaseOperation(operation);
// if this operation has brought in more phase ops for ast transforms, keep recording them
// in master list of other phases and keep processing them for this phase.
recordPhaseOpsInAllOtherPhases(currPhase);
currentPhaseNewOps = newPhaseOperations[currPhase];
}
}
private void doPhaseOperation(Object operation) {
if (operation instanceof PrimaryClassNodeOperation) {
applyToPrimaryClassNodes((PrimaryClassNodeOperation) operation);
} else if (operation instanceof SourceUnitOperation) {
applyToSourceUnits((SourceUnitOperation) operation);
} else {
applyToGeneratedGroovyClasses((GroovyClassOperation) operation);
}
}
private void recordPhaseOpsInAllOtherPhases(int currPhase) {
// apart from current phase, push new operations for every other phase in the master phase ops list
for (int ph = Phases.INITIALIZATION; ph <= Phases.ALL; ph++) {
if (ph != currPhase && !newPhaseOperations[ph].isEmpty()) {
phaseOperations[ph].addAll(newPhaseOperations[ph]);
newPhaseOperations[ph].clear();
}
}
}
private void sortClasses() throws CompilationFailedException {
for (ModuleNode module : this.ast.getModules()) {
module.sortClasses();
}
}
/**
* Dequeues any source units add through addSource and resets the compiler phase
* to initialization.
* <p>
* Note: this does not mean a file is recompiled. If a SourceUnit has already passed
* a phase it is skipped until a higher phase is reached.
*
* @return true if there was a queued source
* @throws CompilationFailedException
*/
protected boolean dequeued() throws CompilationFailedException {
boolean dequeue = !queuedSources.isEmpty();
while (!queuedSources.isEmpty()) {
SourceUnit su = queuedSources.removeFirst();
String name = su.getName();
names.add(name);
sources.put(name, su);
}
if (dequeue) {
gotoPhase(Phases.INITIALIZATION);
}
return dequeue;
}
/**
* Resolves all types
*/
private final SourceUnitOperation resolve = new SourceUnitOperation() {
public void call(SourceUnit source) throws CompilationFailedException {
List<ClassNode> classes = source.ast.getClasses();
for (ClassNode node : classes) {
VariableScopeVisitor scopeVisitor = new VariableScopeVisitor(source);
scopeVisitor.visitClass(node);
resolveVisitor.setClassNodeResolver(classNodeResolver);
resolveVisitor.startResolving(node, source);
}
}
};
private PrimaryClassNodeOperation staticImport = new PrimaryClassNodeOperation() {
public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException {
staticImportVisitor.visitClass(classNode, 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 {
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));
} finally {
if (stream != null) {
try {
stream.close();
} catch (Exception e) {
// Ignore
}
}
}
}
};
/* checks if all needed classes are compiled before generating the bytecode */
private SourceUnitOperation compileCompleteCheck = new SourceUnitOperation() {
public void call(SourceUnit source) throws CompilationFailedException {
List<ClassNode> classes = source.ast.getClasses();
for (ClassNode node : classes) {
CompileUnit cu = node.getCompileUnit();
for (Iterator iter = cu.iterateClassNodeToCompile(); iter.hasNext();) {
String name = (String) iter.next();
SourceUnit su = ast.getScriptSourceLocation(name);
List<ClassNode> classesInSourceUnit = su.ast.getClasses();
StringBuilder message = new StringBuilder();
message
.append("Compilation incomplete: expected to find the class ")
.append(name)
.append(" in ")
.append(su.getName());
if (classesInSourceUnit.isEmpty()) {
message.append(", but the file seems not to contain any classes");
} else {
message.append(", but the file contains the classes: ");
boolean first = true;
for (ClassNode cn : classesInSourceUnit) {
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 {
optimizer.visitClass(classNode, source); // GROOVY-4272: repositioned it here from staticImport
if(!classNode.isSynthetic()) {
GenericsVisitor genericsVisitor = new GenericsVisitor(source);
genericsVisitor.visitClass(classNode);
}
//
// Run the Verifier on the outer class
//
try {
verifier.visitClass(classNode);
} catch (GroovyRuntimeException rpe) {
ASTNode node = rpe.getNode();
getErrorCollector().addError(
new SyntaxException(rpe.getMessage(), node.getLineNumber(), node.getColumnNumber(), node.getLastLineNumber(), node.getLastColumnNumber()),
source
);
}
LabelVerifier lv = new LabelVerifier(source);
lv.visitClass(classNode);
ClassCompletionVerifier completionVerifier = new ClassCompletionVerifier(source);
completionVerifier.visitClass(classNode);
ExtendedVerifier xverifier = new ExtendedVerifier(source);
xverifier.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);
AsmClassGenerator generator = new AsmClassGenerator(source, context, visitor, 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() {
CompilerConfiguration config = getConfiguration();
int computeMaxStackAndFrames = ClassWriter.COMPUTE_MAXS;
if (CompilerConfiguration.isPostJDK7(config.getTargetBytecode())
|| Boolean.TRUE.equals(config.getOptimizationOptions().get("indy"))) {
computeMaxStackAndFrames += ClassWriter.COMPUTE_FRAMES;
}
return new ClassWriter(computeMaxStackAndFrames) {
private ClassNode getClassNode(String name) {
// try classes under compilation
CompileUnit cu = getAST();
ClassNode cn = cu.getClass(name);
if (cn!=null) return cn;
// try inner classes
cn = cu.getGeneratedInnerClass(name);
if (cn!=null) return cn;
// try class loader classes
try {
cn = ClassHelper.make(
cu.getClassLoader().loadClass(name,false,true),
false);
} catch (Exception e) {
throw new GroovyBugError(e);
}
return cn;
}
private ClassNode getCommonSuperClassNode(ClassNode c, ClassNode d) {
// adapted from ClassWriter code
if (c.isDerivedFrom(d)) return d;
if (d.isDerivedFrom(c)) return c;
if (c.isInterface() || d.isInterface()) return ClassHelper.OBJECT_TYPE;
do {
c = c.getSuperClass();
} while (c!=null && !d.isDerivedFrom(c));
if (c==null) return ClassHelper.OBJECT_TYPE;
return c;
}
@Override
protected String getCommonSuperClass(String arg1, String arg2) {
ClassNode a = getClassNode(arg1.replace('/', '.'));
ClassNode b = getClassNode(arg2.replace('/', '.'));
return getCommonSuperClassNode(a,b).getName().replace('.','/');
}
};
}
//---------------------------------------------------------------------------
// 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 abstract static 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 {
for (String name : names) {
SourceUnit source = 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 abstract static class PrimaryClassNodeOperation {
public abstract void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException;
public boolean needSortedInput() {
return false;
}
}
public abstract static class GroovyClassOperation {
public abstract void call(GroovyClass gclass) throws CompilationFailedException;
}
private int getSuperClassCount(ClassNode element) {
int count = 0;
while (element != null) {
count++;
element = element.getSuperClass();
}
return count;
}
private int getSuperInterfaceCount(ClassNode element) {
int count = 1;
ClassNode[] interfaces = element.getInterfaces();
for (ClassNode anInterface : interfaces) {
count = Math.max(count, getSuperInterfaceCount(anInterface) + 1);
}
return count;
}
private List getPrimaryClassNodes(boolean sort) {
List<ClassNode> unsorted = new ArrayList<ClassNode>();
for (ModuleNode module : this.ast.getModules()) {
for (ClassNode classNode : module.getClasses()) {
unsorted.add(classNode);
}
}
if (!sort) return unsorted;
int[] indexClass = new int[unsorted.size()];
int[] indexInterface = new int[unsorted.size()];
{
int i = 0;
for (Iterator<ClassNode> iter = unsorted.iterator(); iter.hasNext(); i++) {
ClassNode element = iter.next();
if (element.isInterface()) {
indexInterface[i] = getSuperInterfaceCount(element);
indexClass[i] = -1;
} else {
indexClass[i] = getSuperClassCount(element);
indexInterface[i] = -1;
}
}
}
List<ClassNode> sorted = getSorted(indexInterface, unsorted);
sorted.addAll(getSorted(indexClass, unsorted));
return sorted;
}
private List<ClassNode> getSorted(int[] index, List<ClassNode> unsorted) {
List<ClassNode> sorted = new ArrayList<ClassNode>(unsorted.size());
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 || (context.phase == phase && !context.phaseComplete)) {
int offset = 1;
Iterator<InnerClassNode> iterator = classNode.getInnerClasses();
while (iterator.hasNext()) {
iterator.next();
offset++;
}
body.call(context, new GeneratorContext(this.ast, offset), classNode);
}
} catch (CompilationFailedException e) {
// fall through, getErrorReporter().failIfErrors() will trigger
} catch (NullPointerException npe) {
GroovyBugError gbe = new GroovyBugError("unexpected NullpointerException", npe);
changeBugText(gbe, context);
throw gbe;
} catch (GroovyBugError e) {
changeBugText(e, context);
throw e;
} catch (NoClassDefFoundError e) {
// effort to get more logging in case a dependency of a class is loaded
// although it shouldn't have
convertUncaughtExceptionToCompilationError(e);
} catch (Exception e) {
convertUncaughtExceptionToCompilationError(e);
}
}
getErrorCollector().failIfErrors();
}
private void convertUncaughtExceptionToCompilationError(final Throwable 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 {
Exception err = e instanceof Exception?((Exception)e):new RuntimeException(e);
getErrorCollector().addError(new ExceptionMessage(err, configuration.getDebug(), this));
}
}
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());
}
for (GroovyClass gclass : this.generatedClasses) {
//
// Get the class and calculate its filesystem name
//
try {
body.call(gclass);
} catch (CompilationFailedException e) {
// fall through, getErrorReporter().failIfErrors() will trigger
} catch (NullPointerException npe) {
throw npe;
} catch (GroovyBugError e) {
changeBugText(e, null);
throw e;
} catch (Exception e) {
throw new GroovyBugError(e);
}
}
getErrorCollector().failIfErrors();
}
private void changeBugText(GroovyBugError e, SourceUnit context) {
e.setBugText("exception in phase '" + getPhaseDescription() + "' in source unit '" + ((context != null) ? context.getName() : "?") + "' " + e.getBugText());
}
public ClassNodeResolver getClassNodeResolver() {
return classNodeResolver;
}
public void setClassNodeResolver(ClassNodeResolver classNodeResolver) {
this.classNodeResolver = classNodeResolver;
}
}