| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.transform.CompilationUnitAware; |
| import org.codehaus.groovy.GroovyBugError; |
| import org.codehaus.groovy.ast.ClassHelper; |
| import org.codehaus.groovy.ast.ClassNode; |
| import org.codehaus.groovy.ast.CompileUnit; |
| import org.codehaus.groovy.ast.GroovyClassVisitor; |
| 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.RuntimeParserException; |
| 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.sc.StaticCompilationMetadataKeys; |
| import org.codehaus.groovy.transform.trait.TraitComposer; |
| import org.objectweb.asm.ClassVisitor; |
| import org.objectweb.asm.ClassWriter; |
| |
| import javax.tools.JavaFileObject; |
| 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.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * 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. |
| */ |
| 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 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; |
| |
| private Set<JavaFileObject> javaCompilationUnitSet = new HashSet<>(); |
| |
| /** |
| * 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.queuedSources = new LinkedList<>(); |
| this.sources = new LinkedHashMap<>(); |
| 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); |
| this.staticImportVisitor = new StaticImportVisitor(); |
| this.optimizer = new OptimizerVisitor(this); |
| |
| initPhaseOperations(); |
| addPhaseOperations(); |
| |
| applyCompilationCustomizers(); |
| |
| this.classgenCallback = null; |
| this.classNodeResolver = new ClassNodeResolver(); |
| } |
| |
| private void initPhaseOperations() { |
| int cnt = Phases.ALL + 1; |
| phaseOperations = new LinkedList[cnt]; |
| newPhaseOperations = new LinkedList[cnt]; |
| for (int i = 0; i < phaseOperations.length; i++) { |
| phaseOperations[i] = new LinkedList<>(); |
| newPhaseOperations[i] = new LinkedList<>(); |
| } |
| } |
| |
| private void addPhaseOperations() { |
| addPhaseOperation(new SourceUnitOperation() { |
| @Override |
| public void call(SourceUnit source) throws CompilationFailedException { |
| source.parse(); |
| } |
| }, Phases.PARSING); |
| addPhaseOperation(convert, Phases.CONVERSION); |
| addPhaseOperation(new PrimaryClassNodeOperation() { |
| @Override |
| 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() { |
| @Override |
| public void call(SourceUnit source, GeneratorContext context, |
| ClassNode classNode) throws CompilationFailedException { |
| if (!classNode.isSynthetic()) { |
| GenericsVisitor genericsVisitor = new GenericsVisitor(source); |
| genericsVisitor.visitClass(classNode); |
| } |
| } |
| }, Phases.SEMANTIC_ANALYSIS); |
| addPhaseOperation(new PrimaryClassNodeOperation() { |
| @Override |
| 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() { |
| @Override |
| public void call(SourceUnit source, GeneratorContext context, |
| ClassNode classNode) throws CompilationFailedException { |
| EnumCompletionVisitor ecv = new EnumCompletionVisitor(CompilationUnit.this, source); |
| ecv.visitClass(classNode); |
| } |
| }, Phases.CANONICALIZATION); |
| addPhaseOperation(new PrimaryClassNodeOperation() { |
| @Override |
| public void call(SourceUnit source, GeneratorContext context, |
| ClassNode classNode) throws CompilationFailedException { |
| Object callback = classNode.getNodeMetaData(StaticCompilationMetadataKeys.DYNAMIC_OUTER_NODE_CALLBACK); |
| if (callback instanceof PrimaryClassNodeOperation) { |
| ((PrimaryClassNodeOperation) callback).call(source, context, classNode); |
| classNode.removeNodeMetaData(StaticCompilationMetadataKeys.DYNAMIC_OUTER_NODE_CALLBACK); |
| } |
| } |
| }, Phases.INSTRUCTION_SELECTION); |
| } |
| |
| private void applyCompilationCustomizers() { |
| for (CompilationCustomizer customizer : configuration.getCompilationCustomizers()) { |
| if (customizer instanceof CompilationUnitAware) { |
| ((CompilationUnitAware) customizer).setCompilationUnit(this); |
| } |
| addPhaseOperation(customizer, customizer.getPhase().getPhaseNumber()); |
| } |
| } |
| |
| /** |
| * 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) { |
| validatePhase(phase); |
| phaseOperations[phase].add(op); |
| } |
| |
| public void addPhaseOperation(PrimaryClassNodeOperation op, int phase) { |
| validatePhase(phase); |
| phaseOperations[phase].add(op); |
| } |
| |
| private static void validatePhase(int phase) { |
| if (phase < 0 || phase > Phases.ALL) throw new IllegalArgumentException("phase " + phase + " is unknown"); |
| } |
| |
| public void addFirstPhaseOperation(PrimaryClassNodeOperation op, int phase) { |
| validatePhase(phase); |
| phaseOperations[phase].add(0, op); |
| } |
| |
| public void addPhaseOperation(GroovyClassOperation op) { |
| phaseOperations[Phases.OUTPUT].addFirst(op); |
| } |
| |
| public void addNewPhaseOperation(SourceUnitOperation op, int phase) { |
| validatePhase(phase); |
| 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}. |
| */ |
| @Override |
| public void configure(CompilerConfiguration configuration) { |
| super.configure(configuration); |
| this.debug = this.configuration.getDebug(); |
| this.configured = true; |
| } |
| |
| /** |
| * 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<GroovyClass> 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[1]; |
| PrimaryClassNodeOperation handler = new PrimaryClassNodeOperation() { |
| @Override |
| 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 = sources.keySet().iterator(); |
| @Override |
| public boolean hasNext() { |
| return nameIterator.hasNext(); |
| } |
| @Override |
| public SourceUnit next() { |
| String name = nameIterator.next(); |
| return sources.get(name); |
| } |
| @Override |
| 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. |
| */ |
| @FunctionalInterface |
| public interface ClassgenCallback { |
| void call(ClassVisitor writer, ClassNode node) throws CompilationFailedException; |
| } |
| |
| /** |
| * Sets a ClassgenCallback. You can have only one, and setting |
| * it to {@code null} removes any existing setting. |
| */ |
| public void setClassgenCallback(ClassgenCallback visitor) { |
| this.classgenCallback = visitor; |
| } |
| |
| public ClassgenCallback getClassgenCallback() { |
| return classgenCallback; |
| } |
| |
| /** |
| * 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. |
| */ |
| @FunctionalInterface |
| public interface ProgressCallback { |
| void call(ProcessingUnit context, int phase) throws CompilationFailedException; |
| } |
| |
| /** |
| * Sets a ProgressCallback. You can have only one, and setting |
| * it to {@code null} removes any existing setting. |
| */ |
| public void setProgressCallback(ProgressCallback callback) { |
| this.progressCallback = callback; |
| } |
| |
| 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(); |
| 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 final 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 final 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 final 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(); |
| |
| try (FileOutputStream stream = new FileOutputStream(path)) { |
| stream.write(bytes, 0, bytes.length); |
| } catch (IOException e) { |
| getErrorCollector().addError(Message.create(e.getMessage(), CompilationUnit.this)); |
| } |
| } |
| }; |
| |
| /* checks if all needed classes are compiled before generating the bytecode */ |
| private final 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<String> iter = cu.iterateClassNodeToCompile(); iter.hasNext();) { |
| String name = 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 final PrimaryClassNodeOperation classgen = new PrimaryClassNodeOperation() { |
| @Override |
| public boolean needSortedInput() { |
| return true; |
| } |
| @Override |
| public void call(SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException { |
| optimizer.visitClass(classNode, source); // GROOVY-4272: repositioned it here from staticImport |
| |
| // |
| // Run the Verifier on the outer class |
| // |
| GroovyClassVisitor visitor = verifier; |
| try { |
| visitor.visitClass(classNode); |
| } catch (RuntimeParserException rpe) { |
| getErrorCollector().addError(new SyntaxException(rpe.getMessage(), rpe.getNode()), source); |
| } |
| |
| visitor = new LabelVerifier(source); |
| visitor.visitClass(classNode); |
| |
| visitor = new InstanceOfVerifier() { |
| @Override |
| protected SourceUnit getSourceUnit() { |
| return source; |
| } |
| }; |
| visitor.visitClass(classNode); |
| |
| visitor = new ClassCompletionVerifier(source); |
| visitor.visitClass(classNode); |
| |
| visitor = new ExtendedVerifier(source); |
| visitor.visitClass(classNode); |
| |
| visitor = null; |
| |
| // 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 classVisitor = 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, classVisitor, sourceName); |
| |
| // |
| // Run the generation and create the class (if required) |
| // |
| generator.visitClass(classNode); |
| |
| byte[] bytes = ((ClassWriter) classVisitor).toByteArray(); |
| generatedClasses.add(new GroovyClass(classNode.getName(), bytes)); |
| |
| // |
| // Handle any callback that's been set |
| // |
| if (CompilationUnit.this.classgenCallback != null) { |
| classgenCallback.call(classVisitor, classNode); |
| } |
| |
| // |
| // Recurse for inner classes |
| // |
| LinkedList<ClassNode> innerClasses = generator.getInnerClasses(); |
| while (!innerClasses.isEmpty()) { |
| classgen.call(source, context, innerClasses.removeFirst()); |
| } |
| } |
| }; |
| |
| protected ClassVisitor createClassVisitor() { |
| CompilerConfiguration config = getConfiguration(); |
| int computeMaxStackAndFrames = ClassWriter.COMPUTE_MAXS; |
| if (CompilerConfiguration.isPostJDK7(config.getTargetBytecode()) || config.isIndyEnabled()) { |
| 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; |
| ClassNodeResolver.LookupResult lookupResult = getClassNodeResolver().resolveName(name, CompilationUnit.this); |
| return lookupResult == null ? null : lookupResult.getClassNode(); |
| } |
| 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 final 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 : sources.keySet()) { |
| 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 applyToPrimaryClassNodes 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 static 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<ClassNode> getPrimaryClassNodes(boolean sort) { |
| List<ClassNode> unsorted = new ArrayList<ClassNode>(); |
| for (ModuleNode module : this.ast.getModules()) { |
| unsorted.addAll(module.getClasses()); |
| } |
| |
| if (!sort) return unsorted; |
| |
| int unsortedSize = unsorted.size(); |
| int[] indexClass = new int[unsortedSize]; |
| int[] indexInterface = new int[unsortedSize]; |
| { |
| 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 static List<ClassNode> getSorted(int[] index, List<ClassNode> unsorted) { |
| int unsortedSize = unsorted.size(); |
| List<ClassNode> sorted = new ArrayList<ClassNode>(unsortedSize); |
| for (int i = 0; i < unsortedSize; i++) { |
| int min = -1; |
| for (int j = 0; j < unsortedSize; j++) { |
| if (index[j] == -1) continue; |
| if (min == -1 || 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 { |
| for (ClassNode classNode : getPrimaryClassNodes(body.needSortedInput())) { |
| SourceUnit context = null; |
| try { |
| context = classNode.getModule().getContext(); |
| if (context == null || context.phase < phase || (context.phase == phase && !context.phaseComplete)) { |
| int offset = 1; |
| for (Iterator<InnerClassNode> iterator = classNode.getInnerClasses(); 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 | Exception e) { |
| // effort to get more logging in case a dependency of a class is loaded |
| // although it shouldn't have |
| 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; |
| } |
| |
| public Set<JavaFileObject> getJavaCompilationUnitSet() { |
| return javaCompilationUnitSet; |
| } |
| |
| public void addJavaCompilationUnits(Set<JavaFileObject> javaCompilationUnitSet) { |
| this.javaCompilationUnitSet.addAll(javaCompilationUnitSet); |
| } |
| } |