/* * Copyright 2003-2012 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 groovyjarjarasm.asm.ClassVisitor; import groovyjarjarasm.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.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import org.codehaus.groovy.GroovyBugError; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.ClassHelper; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.CompileUnit; import org.codehaus.groovy.ast.InnerClassNode; import org.codehaus.groovy.ast.ModuleNode; import org.codehaus.groovy.classgen.AsmClassGenerator; import org.codehaus.groovy.classgen.ClassCompletionVerifier; import org.codehaus.groovy.classgen.EnumCompletionVisitor; import org.codehaus.groovy.classgen.EnumVisitor; import org.codehaus.groovy.classgen.ExtendedVerifier; import org.codehaus.groovy.classgen.GeneratorContext; import org.codehaus.groovy.classgen.InnerClassCompletionVisitor; import org.codehaus.groovy.classgen.InnerClassVisitor; import org.codehaus.groovy.classgen.VariableScopeVisitor; import org.codehaus.groovy.classgen.Verifier; 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; /** * 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). <br/><br/> * * 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> * @version $Id$ */ 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) { // GRECLIPSE extra param this(configuration, security, loader, null,true,null); } // GRECLIPSE start /** * Note: globalASTransformExclude param is ignored it is only implemented for Groovy 23 and up. * This is just a stub to maintain binary compatibility on the calling side. */ public CompilationUnit(CompilerConfiguration configuration, CodeSource security, GroovyClassLoader loader, GroovyClassLoader transformLoader, boolean allowTransforms, String localTransformsToRunOnReconcile, String globalASTransformExclude) { this(configuration, security, loader, transformLoader, allowTransforms, localTransformsToRunOnReconcile); } // GRECLIPSE end // GRECLIPSE extra param /** * 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, boolean allowTransforms, String localTransformsToRunOnReconcile) { super(configuration, loader, null); this.allowTransforms = allowTransforms; 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); // GRECLIPSE start if (localTransformsToRunOnReconcile==null) { this.localTransformsToRunOnReconcile = Collections.emptyList(); } else { this.localTransformsToRunOnReconcile=new ArrayList<String>(); try { StringTokenizer st = new StringTokenizer(localTransformsToRunOnReconcile,","); while (st.hasMoreElements()) { String classname = st.nextToken(); this.localTransformsToRunOnReconcile.add(classname); } } catch (Exception e) { // presumed security exception } } // GRECLIPSE end 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(compileCompleteCheck, Phases.CANONICALIZATION); addPhaseOperation(classgen, Phases.CLASS_GENERATION); // GRECLIPSE: start: skip output phase // addPhaseOperation(output); 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) { addPhaseOperation(customizer, customizer.getPhase().getPhaseNumber()); } } this.classgenCallback = null; this.classNodeResolver = new ClassNodeResolver(); } // GRECLIPSE: new method: force the phase on public void ensureASTTransformVisitorAdded() { ASTTransformationVisitor.addPhaseOperations(this); } /** * 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 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); } // GRECLIPSE: new method: can be called to prevent classfile output (so only use if something else is taking charge of output) public boolean removeOutputPhaseOperation() { return phaseOperations[Phases.OUTPUT].remove(output); } /** * 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; } // GRECLIPSE: start if (iterating) { GroovyBugError gbe = new GroovyBugError("Queuing new source whilst already iterating. Queued source is '"+source.getName()+"'"); gbe.printStackTrace(); throw gbe; } // end 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; } // GRECLIPSE: start public interface ProgressListener { void parseComplete(int phase, String sourceUnitName); void generateComplete(int phase, ClassNode classNode); } private ProgressListener getProgressListener() { return this.listener; } public void setProgressListener(ProgressListener listener) { this.listener = listener; } private ProgressListener listener; // end /** * 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(); // GRECLIPSE: start if (iterating) { GroovyBugError gbe = new GroovyBugError("Damaging 'names' whilst already iterating. Name getting added is '"+su.getName()+"'"); gbe.printStackTrace(); throw gbe; } // end 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(); StringBuffer message = new StringBuffer(); 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) // // GRECLIPSE: if there are errors, don't generate code. // code gen can fail unexpectedly if there was an earlier error. if (!source.getErrorCollector().hasErrors()) { // end generator.visitClass(classNode); byte[] bytes = ((ClassWriter) visitor).toByteArray(); /// GRECLIPSE: start: added classNode, sourceUnit /*old{ generatedClasses.add(new GroovyClass(classNode.getName(), bytes)); }*/ // newcode generatedClasses.add(new GroovyClass(classNode.getName(), bytes, classNode, source)); // end // // 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()); } // GRECLIPSE: if there are errors, don't generate code } // end } }; protected ClassVisitor createClassVisitor() { CompilerConfiguration config = getConfiguration(); int computeMaxStackAndFrames = ClassWriter.COMPUTE_MAXS; if (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; } // GRECLIPSE: new field private boolean iterating = false; /** * 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 { // GRECLIPSE: start try { iterating = true; // end for (String name : names) { SourceUnit source = sources.get(name); if ((source.phase < phase) || (source.phase == phase && !source.phaseComplete)) { try { body.call(source); // GRECLIPSE: start if (phase==Phases.CONVERSION && getProgressListener()!=null && body==phaseOperations[phase].getLast()) { getProgressListener().parseComplete(phase,name); } // end } 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; } } } // GRECLIPSE: start } finally { iterating = false; } // end 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) { if (sort==true) { List<ModuleNode> sortedModules = this.ast.getSortedModules(); if (sortedModules!=null) { return sortedModules; } } // FIXASC (groovychange) rewritten /*old{ List 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); } } */ // new List<ClassNode> unsorted = new ArrayList<ClassNode>(); for (ModuleNode module: this.ast.getModules()) { unsorted.addAll(module.getClasses()); } // FIXASC (groovychange) end if (!sort) return unsorted; // GRECLIPSE: start: rewritten sort algorithm /*old{ 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)); */ // newcode: // Sort them by how many types are in their hierarchy, but all interfaces first. // Algorithm: // Create a list of integers. Each integer captures the index into the unsorted // list (bottom 16bits) and the count of how many types are in that types // hierarchy (top 16bits). For classes the count is augmented by 2000 so that // when sorting the classes will come out after the interfaces. // This list of integers is sorted. We then just go through it and for the // lower 16bits of each entry (0xffff) that is the index of the next value to // pull from the unsorted list and put into the sorted list. // Will break down if more than 2000 interfaces in the type hierarchy for an // individual type, or a project contains > 65535 files... but if you've got // that kind of setup, you have other problems... List<Integer> countIndexPairs = new ArrayList<Integer>(); { int i = 0; for (Iterator iter = unsorted.iterator(); iter.hasNext(); i++) { ClassNode node = (ClassNode) iter.next(); if (node.isInterface()) { countIndexPairs.add((getSuperInterfaceCount(node)<<16)+i); } else { countIndexPairs.add(((getSuperClassCount(node)+2000)<<16)+i); } } } Collections.sort(countIndexPairs); List sorted = new ArrayList(); for (int i: countIndexPairs) { sorted.add(unsorted.get(i&0xffff)); } this.ast.setSortedModules(sorted); // end return sorted; } @SuppressWarnings("unused") 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 { // GRECLIPSE: start /*old{ Iterator classNodes = getPrimaryClassNodes(body.needSortedInput()).iterator(); }*/ // newcode List primaryClassNodes = getPrimaryClassNodes(body.needSortedInput()); Iterator classNodes = primaryClassNodes.iterator(); // end while (classNodes.hasNext()) { SourceUnit context = null; try { ClassNode classNode = (ClassNode) classNodes.next(); context = classNode.getModule().getContext(); // GRECLIPSE get to the bottom of this - why are operations running multiple times that should only run once? 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); /**** 1.8.6 seemed to adjust this code, who is impacted? can I remove our change below? body.call(context, new GeneratorContext(this.ast), classNode); // GRECLIPSE: start if (phase==Phases.CLASS_GENERATION && getProgressListener()!=null && body==phaseOperations[phase].getLast()) { getProgressListener().generateComplete(phase,classNode); } // FIXASC (groovychange) end *****/ } } catch (CompilationFailedException e) { // fall through, getErrorReporter().failIfErrors() will trigger } 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()); } 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; } // GRECLIPSE: start public void setResolveVisitor(ResolveVisitor resolveVisitor2) { this.resolveVisitor = resolveVisitor2; } public ResolveVisitor getResolveVisitor() { return this.resolveVisitor; } public String toString() { if (sources==null || sources.isEmpty()) return super.toString(); Set s = sources.keySet(); for (Object o: s) { return "CompilationUnit: source is " + o.toString(); } return "CompilationUnit: null"; } public boolean allowTransforms = true; public boolean isReconcile = false; public List<String> localTransformsToRunOnReconcile = null; /** * Slightly modifies the behaviour of the phases based on what the caller really needs. Some invocations of the compilation * infrastructure don't need the bytecode, so we can skip creating it, they would rather have a more 'source like' AST. * * @param isReconcile is this a reconciling compile? */ public void tweak(boolean isReconcile) { // Cant do this for field initializers. They need to be in the constructor in order for them to // be correctly visited by the verifier and have certain optimizations performed (creating returns) if (isReconcile) { verifier.inlineStaticFieldInitializersIntoClinit=false; // verifier.inlineFieldInitializersIntoInit=false; staticImportVisitor.isReconcile = true; } else { verifier.inlineStaticFieldInitializersIntoClinit=true; // verifier.inlineFieldInitializersIntoInit=true; } this.isReconcile = isReconcile; } // end }