http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/control/CompilationUnit.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/control/CompilationUnit.java b/src/main/java/org/codehaus/groovy/control/CompilationUnit.java new file mode 100644 index 0000000..dab0e55 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/control/CompilationUnit.java @@ -0,0 +1,1167 @@ +/* + * 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.lang.GroovyRuntimeException; +import groovy.transform.CompilationUnitAware; +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; +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 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.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +/** + * 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:[email protected]">Chris Poirier</a> + * @author <a href="mailto:[email protected]">Jochen Theodorou</a> + * @author <a href="mailto:[email protected]">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() { + @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() { + 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); + 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); + + // 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 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(); + + 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 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 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 final 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 + + // + // 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 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 : 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 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()) { + for (ClassNode classNode : module.getClasses()) { + unsorted.add(classNode); + } + } + + 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 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; + } + +}
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/control/CompilePhase.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/control/CompilePhase.java b/src/main/java/org/codehaus/groovy/control/CompilePhase.java new file mode 100644 index 0000000..7b25fed --- /dev/null +++ b/src/main/java/org/codehaus/groovy/control/CompilePhase.java @@ -0,0 +1,118 @@ +/* + * 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; + +/** +* The phases of the GroovyCompiler. This is an enum facade on top of the +* Phases object. In general, prefer using this object over Phases. +* +* @author Hamlet D'Arcy +*/ +public enum CompilePhase { + + /** + * source files are opened and environment configured + */ + INITIALIZATION(Phases.INITIALIZATION), + + /** + * the grammar is used to to produce tree of tokens representing the source code + */ + PARSING(Phases.PARSING), + + /** + * An abstract syntax tree (AST) is created from token trees + */ + CONVERSION(Phases.CONVERSION), + + /** + * Performs consistency and validity checks that the grammar can't check for, and resolves classes + */ + SEMANTIC_ANALYSIS(Phases.SEMANTIC_ANALYSIS), + + /** + * Complete building the AST + */ + CANONICALIZATION(Phases.CANONICALIZATION), + + /** + * instruction set is chosen, for example java5 or pre java5 + */ + INSTRUCTION_SELECTION(Phases.INSTRUCTION_SELECTION), + + /** + * creates the binary output in memory + */ + CLASS_GENERATION(Phases.CLASS_GENERATION), + + /** + * write the binary output to the file system + */ + OUTPUT(Phases.OUTPUT), + + /** + * Perform any last cleanup + */ + FINALIZATION(Phases.FINALIZATION), + ; + + /** + * The phases as an array, with a null entry. + */ + public static CompilePhase[] phases = { + null, + INITIALIZATION, + PARSING, + CONVERSION, + SEMANTIC_ANALYSIS, + CANONICALIZATION, + INSTRUCTION_SELECTION, + CLASS_GENERATION, + OUTPUT, + FINALIZATION, + }; + + int phaseNumber; + CompilePhase(int phaseNumber) { + this.phaseNumber = phaseNumber; + } + + /** + * Returns the underlieng integer Phase number. + */ + public int getPhaseNumber() { + return phaseNumber; + } + + /** + * Returns the CompilePhase for the given integer phase number. + * @param phaseNumber + * the phase number + * @return + * the CompilePhase or null if not found + */ + public static CompilePhase fromPhaseNumber(int phaseNumber) { + for (CompilePhase phase : values()) { + if (phase.phaseNumber == phaseNumber) { + return phase; + } + } + return null; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/control/CompilerConfiguration.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/control/CompilerConfiguration.java b/src/main/java/org/codehaus/groovy/control/CompilerConfiguration.java new file mode 100644 index 0000000..9f6babe --- /dev/null +++ b/src/main/java/org/codehaus/groovy/control/CompilerConfiguration.java @@ -0,0 +1,934 @@ +/* + * 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 org.apache.groovy.util.Maps; +import org.codehaus.groovy.control.customizers.CompilationCustomizer; +import org.codehaus.groovy.control.io.NullWriter; +import org.codehaus.groovy.control.messages.WarningMessage; +import org.objectweb.asm.Opcodes; + +import java.io.File; +import java.io.PrintWriter; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.StringTokenizer; + +/** + * Compilation control flags and coordination stuff. + * + * @author <a href="mailto:[email protected]">Chris Poirier</a> + * @author <a href="mailto:[email protected]">Jochen Theodorou</a> + * @author <a href="mailto:[email protected]">Jim White</a> + * @author <a href="mailto:[email protected]">Cedric Champeau</a> + */ + +public class CompilerConfiguration { + + /** This (<code>"indy"</code>) is the Optimization Option value for enabling <code>invokedynamic</code> complilation. */ + public static final String INVOKEDYNAMIC = "indy"; + + /** This (<code>"1.4"</code>) is the value for targetBytecode to compile for a JDK 1.4. **/ + public static final String JDK4 = "1.4"; + /** This (<code>"1.5"</code>) is the value for targetBytecode to compile for a JDK 1.5. **/ + public static final String JDK5 = "1.5"; + /** This (<code>"1.6"</code>) is the value for targetBytecode to compile for a JDK 1.6. **/ + public static final String JDK6 = "1.6"; + /** This (<code>"1.7"</code>) is the value for targetBytecode to compile for a JDK 1.7. **/ + public static final String JDK7 = "1.7"; + /** This (<code>"1.8"</code>) is the value for targetBytecode to compile for a JDK 1.8. **/ + public static final String JDK8 = "1.8"; + + /** This (<code>"1.5"</code>) is the value for targetBytecode to compile for a JDK 1.5 or later JVM. **/ + public static final String POST_JDK5 = JDK5; // for backwards compatibility + + /** This (<code>"1.4"</code>) is the value for targetBytecode to compile for a JDK 1.4 JVM. **/ + public static final String PRE_JDK5 = JDK4; + + /** + * JDK version to bytecode version mapping + */ + public static final Map<String, Integer> JDK_TO_BYTECODE_VERSION_MAP = Maps.of( + JDK4, Opcodes.V1_4, + JDK5, Opcodes.V1_5, + JDK6, Opcodes.V1_6, + JDK7, Opcodes.V1_7, + JDK8, Opcodes.V1_8 + ); + + /** An array of the valid targetBytecode values **/ + public static final String[] ALLOWED_JDKS = JDK_TO_BYTECODE_VERSION_MAP.keySet().toArray(new String[0]); + + // Just call getVMVersion() once. + public static final String CURRENT_JVM_VERSION = getVMVersion(); + + private static final String GROOVY_ANTLR4_OPT = "groovy.antlr4"; + + /** + * The default source encoding + */ + public static final String DEFAULT_SOURCE_ENCODING = "UTF-8"; + + // Static initializers are executed in text order, + // therefore we must do this one last! + /** + * A convenience for getting a default configuration. Do not modify it! + * See {@link #CompilerConfiguration(Properties)} for an example on how to + * make a suitable copy to modify. But if you're really starting from a + * default context, then you probably just want <code>new CompilerConfiguration()</code>. + */ + public static final CompilerConfiguration DEFAULT = new CompilerConfiguration(); + + /** + * See {@link WarningMessage} for levels. + */ + private int warningLevel; + + /** + * Encoding for source files + */ + private String sourceEncoding; + + /** + * The <code>PrintWriter</code> does nothing. + */ + private PrintWriter output; + + /** + * Directory into which to write classes + */ + private File targetDirectory; + + /** + * Classpath for use during compilation + */ + private LinkedList<String> classpath; + + /** + * If true, the compiler should produce action information + */ + private boolean verbose; + + /** + * If true, debugging code should be activated + */ + private boolean debug; + + /** + * If true, generates metadata for reflection on method parameters + */ + private boolean parameters = false; + + /** + * The number of non-fatal errors to allow before bailing + */ + private int tolerance; + + /** + * Base class name for scripts (must derive from Script) + */ + private String scriptBaseClass; + + private ParserPluginFactory pluginFactory; + + /** + * extension used to find a groovy file + */ + private String defaultScriptExtension; + + /** + * extensions used to find a groovy files + */ + private Set<String> scriptExtensions = new LinkedHashSet<String>(); + + /** + * if set to true recompilation is enabled + */ + private boolean recompileGroovySource; + + /** + * sets the minimum of time after a script can be recompiled. + */ + private int minimumRecompilationInterval; + + /** + * sets the bytecode version target + */ + private String targetBytecode; + + /** + * options for joint compilation (null by default == no joint compilation) + */ + private Map<String, Object> jointCompilationOptions; + + /** + * options for optimizations (empty map by default) + */ + private Map<String, Boolean> optimizationOptions; + + private final List<CompilationCustomizer> compilationCustomizers = new LinkedList<CompilationCustomizer>(); + + /** + * Sets a list of global AST transformations which should not be loaded even if they are + * defined in META-INF/org.codehaus.groovy.transform.ASTTransformation files. By default, + * none is disabled. + */ + private Set<String> disabledGlobalASTTransformations; + + private BytecodeProcessor bytecodePostprocessor; + + /** + * defines if antlr2 parser should be used or the antlr4 one if + * no factory is set yet + */ + private ParserVersion parserVersion = ParserVersion.V_2; + + /** + * Sets the Flags to defaults. + */ + public CompilerConfiguration() { + // + // Set in safe defaults + + setWarningLevel(WarningMessage.LIKELY_ERRORS); + setOutput(null); + setTargetDirectory((File) null); + setClasspath(""); + setVerbose(false); + setDebug(false); + setParameters(safeGetSystemProperty("groovy.parameters") != null); + setTolerance(10); + setScriptBaseClass(null); + setRecompileGroovySource(false); + setMinimumRecompilationInterval(100); + setTargetBytecode(safeGetSystemProperty("groovy.target.bytecode", getVMVersion())); + setDefaultScriptExtension(safeGetSystemProperty("groovy.default.scriptExtension", ".groovy")); + + // Source file encoding + String encoding = safeGetSystemProperty("file.encoding", DEFAULT_SOURCE_ENCODING); + encoding = safeGetSystemProperty("groovy.source.encoding", encoding); + setSourceEncoding(encoding); + + try { + setOutput(new PrintWriter(System.err)); + } catch (Exception e) { + // IGNORE + } + + + String target = safeGetSystemProperty("groovy.target.directory"); + if (target != null) { + setTargetDirectory(target); + } + + boolean indy = false; + try { + indy = Boolean.getBoolean("groovy.target.indy"); + } catch (Exception e) { + // IGNORE + } + if (DEFAULT!=null && Boolean.TRUE.equals(DEFAULT.getOptimizationOptions().get(INVOKEDYNAMIC))) { + indy = true; + } + Map options = new HashMap<String,Boolean>(3); + if (indy) { + options.put(INVOKEDYNAMIC, Boolean.TRUE); + } + setOptimizationOptions(options); + + try { + String groovyAntlr4Opt = System.getProperty(GROOVY_ANTLR4_OPT); + + this.parserVersion = !Boolean.valueOf(groovyAntlr4Opt) + ? ParserVersion.V_2 + : ParserVersion.V_4; + } catch (Exception e) { + // IGNORE + } + } + + /** + * Retrieves a System property, or null if any of the following exceptions occur. + * <ul> + * <li>SecurityException - if a security manager exists and its checkPropertyAccess method doesn't allow access to the specified system property.</li> + * <li>NullPointerException - if key is null.</li> + * <li>IllegalArgumentException - if key is empty.</li> + * </ul> + * @param key the name of the system property. + * @return value of the system property or null + */ + private static String safeGetSystemProperty(String key){ + return safeGetSystemProperty(key, null); + } + + /** + * Retrieves a System property, or null if any of the following exceptions occur (Warning: Exception messages are + * suppressed). + * <ul> + * <li>SecurityException - if a security manager exists and its checkPropertyAccess method doesn't allow access to the specified system property.</li> + * <li>NullPointerException - if key is null.</li> + * <li>IllegalArgumentException - if key is empty.</li> + * </ul> + * @param key the name of the system property. + * @param def a default value. + * @return value of the system property or null + */ + private static String safeGetSystemProperty(String key, String def){ + try { + return System.getProperty(key, def); + } catch (SecurityException t){ + // suppress exception + } catch (NullPointerException t){ + // suppress exception + } catch (IllegalArgumentException t){ + // suppress exception + } + return def; + } + + /** + * Copy constructor. Use this if you have a mostly correct configuration + * for your compilation but you want to make a some changes programatically. + * An important reason to prefer this approach is that your code will most + * likely be forward compatible with future changes to this configuration API. + * <p> + * An example of this copy constructor at work: + * <pre> + * // In all likelihood there is already a configuration in your code's context + * // for you to copy, but for the sake of this example we'll use the global default. + * CompilerConfiguration myConfiguration = new CompilerConfiguration(CompilerConfiguration.DEFAULT); + * myConfiguration.setDebug(true); + *</pre> + * + * @param configuration The configuration to copy. + */ + public CompilerConfiguration(CompilerConfiguration configuration) { + setWarningLevel(configuration.getWarningLevel()); + setOutput(configuration.getOutput()); + setTargetDirectory(configuration.getTargetDirectory()); + setClasspathList(new LinkedList<String>(configuration.getClasspath())); + setVerbose(configuration.getVerbose()); + setDebug(configuration.getDebug()); + setParameters(configuration.getParameters()); + setTolerance(configuration.getTolerance()); + setScriptBaseClass(configuration.getScriptBaseClass()); + setRecompileGroovySource(configuration.getRecompileGroovySource()); + setMinimumRecompilationInterval(configuration.getMinimumRecompilationInterval()); + setTargetBytecode(configuration.getTargetBytecode()); + setDefaultScriptExtension(configuration.getDefaultScriptExtension()); + setSourceEncoding(configuration.getSourceEncoding()); + setTargetDirectory(configuration.getTargetDirectory()); + Map<String, Object> jointCompilationOptions = configuration.getJointCompilationOptions(); + if (jointCompilationOptions != null) { + jointCompilationOptions = new HashMap<String, Object>(jointCompilationOptions); + } + setJointCompilationOptions(jointCompilationOptions); + setPluginFactory(configuration.getPluginFactory()); + setScriptExtensions(configuration.getScriptExtensions()); + setOptimizationOptions(new HashMap<String, Boolean>(configuration.getOptimizationOptions())); + } + + /** + * Sets the Flags to the specified configuration, with defaults + * for those not supplied. + * Note that those "defaults" here do <em>not</em> include checking the + * settings in {@link System#getProperties()} in general, only file.encoding, + * groovy.target.directory and groovy.source.encoding are. + * <p> + * If you want to set a few flags but keep Groovy's default + * configuration behavior then be sure to make your settings in + * a Properties that is backed by <code>System.getProperties()</code> (which + * is done using this constructor). That might be done like this: + * <pre> + * Properties myProperties = new Properties(System.getProperties()); + * myProperties.setProperty("groovy.output.debug", "true"); + * myConfiguration = new CompilerConfiguration(myProperties); + * </pre> + * And you also have to contend with a possible SecurityException when + * getting the system properties (See {@link java.lang.System#getProperties()}). + * A safer approach would be to copy a default + * CompilerConfiguration and make your changes there using the setter: + * <pre> + * // In all likelihood there is already a configuration for you to copy, + * // but for the sake of this example we'll use the global default. + * CompilerConfiguration myConfiguration = new CompilerConfiguration(CompilerConfiguration.DEFAULT); + * myConfiguration.setDebug(true); + * </pre> + * <p> + * <table summary="Groovy Compiler Configuration Properties"> + * <tr> + * <th>Property Key</th><th>Get/Set Property Name</th> + * </tr> + * <tr> + * <td><code>"groovy.warnings"</code></td><td>{@link #getWarningLevel}</td></tr> + * <tr><td><code>"groovy.source.encoding"</code></td><td>{@link #getSourceEncoding}</td></tr> + * <tr><td><code>"groovy.target.directory"</code></td><td>{@link #getTargetDirectory}</td></tr> + * <tr><td><code>"groovy.target.bytecode"</code></td><td>{@link #getTargetBytecode}</td></tr> + * <tr><td><code>"groovy.classpath"</code></td><td>{@link #getClasspath}</td></tr> + * <tr><td><code>"groovy.output.verbose"</code></td><td>{@link #getVerbose}</td></tr> + * <tr><td><code>"groovy.output.debug"</code></td><td>{@link #getDebug}</td></tr> + * <tr><td><code>"groovy.errors.tolerance"</code></td><td>{@link #getTolerance}</td></tr> + * <tr><td><code>"groovy.script.extension"</code></td><td>{@link #getDefaultScriptExtension}</td></tr> + * <tr><td><code>"groovy.script.base"</code></td><td>{@link #getScriptBaseClass}</td></tr> + * <tr><td><code>"groovy.recompile"</code></td><td>{@link #getRecompileGroovySource}</td></tr> + * <tr><td><code>"groovy.recompile.minimumInterval"</code></td><td>{@link #getMinimumRecompilationInterval}</td></tr> + * <tr><td> + * </tr> + * </table> + * + * @param configuration The properties to get flag values from. + */ + public CompilerConfiguration(Properties configuration) throws ConfigurationException { + this(); + configure(configuration); + } + + /** + * Checks if the specified bytecode version string represents a JDK 1.5+ compatible + * bytecode version. + * @param bytecodeVersion the bytecode version string (1.4, 1.5, 1.6, 1.7 or 1.8) + * @return true if the bytecode version is JDK 1.5+ + */ + public static boolean isPostJDK5(String bytecodeVersion) { + return new BigDecimal(bytecodeVersion).compareTo(new BigDecimal(JDK5)) >= 0; + } + + /** + * Checks if the specified bytecode version string represents a JDK 1.7+ compatible + * bytecode version. + * @param bytecodeVersion the bytecode version string (1.4, 1.5, 1.6, 1.7 or 1.8) + * @return true if the bytecode version is JDK 1.7+ + */ + public static boolean isPostJDK7(String bytecodeVersion) { + return new BigDecimal(bytecodeVersion).compareTo(new BigDecimal(JDK7)) >= 0; + } + + /** + * Method to configure a CompilerConfiguration by using Properties. + * For a list of available properties look at {@link #CompilerConfiguration(Properties)}. + * @param configuration The properties to get flag values from. + */ + public void configure(Properties configuration) throws ConfigurationException { + String text = null; + int numeric = 0; + + // + // Warning level + + numeric = getWarningLevel(); + try { + text = configuration.getProperty("groovy.warnings", "likely errors"); + numeric = Integer.parseInt(text); + } catch (NumberFormatException e) { + text = text.toLowerCase(); + if (text.equals("none")) { + numeric = WarningMessage.NONE; + } + else if (text.startsWith("likely")) { + numeric = WarningMessage.LIKELY_ERRORS; + } + else if (text.startsWith("possible")) { + numeric = WarningMessage.POSSIBLE_ERRORS; + } + else if (text.startsWith("paranoia")) { + numeric = WarningMessage.PARANOIA; + } + else { + throw new ConfigurationException("unrecognized groovy.warnings: " + text); + } + } + setWarningLevel(numeric); + + // + // Source file encoding + // + text = configuration.getProperty("groovy.source.encoding"); + if (text == null) { + text = configuration.getProperty("file.encoding", "US-ASCII"); + } + setSourceEncoding(text); + + // + // Target directory for classes + // + text = configuration.getProperty("groovy.target.directory"); + if (text != null) setTargetDirectory(text); + + text = configuration.getProperty("groovy.target.bytecode"); + if (text != null) setTargetBytecode(text); + + // + // Classpath + // + text = configuration.getProperty("groovy.classpath"); + if (text != null) setClasspath(text); + + // + // Verbosity + // + text = configuration.getProperty("groovy.output.verbose"); + if (text != null && text.equalsIgnoreCase("true")) setVerbose(true); + + // + // Debugging + // + text = configuration.getProperty("groovy.output.debug"); + if (text != null && text.equalsIgnoreCase("true")) setDebug(true); + + // + // Parameters + // + setParameters(configuration.getProperty("groovy.parameters") != null); + + // + // Tolerance + // + numeric = 10; + try { + text = configuration.getProperty("groovy.errors.tolerance", "10"); + numeric = Integer.parseInt(text); + } catch (NumberFormatException e) { + throw new ConfigurationException(e); + } + setTolerance(numeric); + + // + // Script Base Class + // + text = configuration.getProperty("groovy.script.base"); + if (text!=null) setScriptBaseClass(text); + + // + // recompilation options + // + text = configuration.getProperty("groovy.recompile"); + if (text != null) { + setRecompileGroovySource(text.equalsIgnoreCase("true")); + } + + numeric = 100; + try { + text = configuration.getProperty("groovy.recompile.minimumIntervall"); + if (text==null) text = configuration.getProperty("groovy.recompile.minimumInterval"); + if (text!=null) { + numeric = Integer.parseInt(text); + } else { + numeric = 100; + } + } catch (NumberFormatException e) { + throw new ConfigurationException(e); + } + setMinimumRecompilationInterval(numeric); + + // disabled global AST transformations + text = configuration.getProperty("groovy.disabled.global.ast.transformations"); + if (text!=null) { + String[] classNames = text.split(",\\s*}"); + Set<String> blacklist = new HashSet<String>(Arrays.asList(classNames)); + setDisabledGlobalASTTransformations(blacklist); + } + } + + /** + * Gets the currently configured warning level. See {@link WarningMessage} + * for level details. + */ + public int getWarningLevel() { + return this.warningLevel; + } + + /** + * Sets the warning level. See {@link WarningMessage} for level details. + */ + public void setWarningLevel(int level) { + if (level < WarningMessage.NONE || level > WarningMessage.PARANOIA) { + this.warningLevel = WarningMessage.LIKELY_ERRORS; + } + else { + this.warningLevel = level; + } + } + + /** + * Gets the currently configured source file encoding. + */ + public String getSourceEncoding() { + return this.sourceEncoding; + } + + /** + * Sets the encoding to be used when reading source files. + */ + public void setSourceEncoding(String encoding) { + if (encoding == null) encoding = DEFAULT_SOURCE_ENCODING; + this.sourceEncoding = encoding; + } + + /** + * Gets the currently configured output writer. + * @deprecated not used anymore + */ + @Deprecated + public PrintWriter getOutput() { + return this.output; + } + + /** + * Sets the output writer. + * @deprecated not used anymore, has no effect + */ + @Deprecated + public void setOutput(PrintWriter output) { + if (output == null) { + this.output = new PrintWriter(NullWriter.DEFAULT); + } + else { + this.output = output; + } + } + + /** + * Gets the target directory for writing classes. + */ + public File getTargetDirectory() { + return this.targetDirectory; + } + + /** + * Sets the target directory. + */ + public void setTargetDirectory(String directory) { + if (directory != null && directory.length() > 0) { + this.targetDirectory = new File(directory); + } else { + this.targetDirectory = null; + } + } + + /** + * Sets the target directory. + */ + public void setTargetDirectory(File directory) { + this.targetDirectory = directory; + } + + /** + * @return the classpath + */ + public List<String> getClasspath() { + return this.classpath; + } + + /** + * Sets the classpath. + */ + public void setClasspath(String classpath) { + this.classpath = new LinkedList<String>(); + StringTokenizer tokenizer = new StringTokenizer(classpath, File.pathSeparator); + while (tokenizer.hasMoreTokens()) { + this.classpath.add(tokenizer.nextToken()); + } + } + + /** + * sets the classpath using a list of Strings + * @param parts list of strings containing the classpath parts + */ + public void setClasspathList(List<String> parts) { + this.classpath = new LinkedList<String>(parts); + } + + /** + * Returns true if verbose operation has been requested. + */ + public boolean getVerbose() { + return this.verbose; + } + + /** + * Turns verbose operation on or off. + */ + public void setVerbose(boolean verbose) { + this.verbose = verbose; + } + + /** + * Returns true if debugging operation has been requested. + */ + public boolean getDebug() { + return this.debug; + } + + /** + * Returns true if parameter metadata generation has been enabled. + */ + public boolean getParameters() { + return this.parameters; + } + + /** + * Turns debugging operation on or off. + */ + public void setDebug(boolean debug) { + this.debug = debug; + } + + /** + * Turns parameter metadata generation on or off. + */ + public void setParameters(boolean parameters) { + this.parameters = parameters; + } + + /** + * Returns the requested error tolerance. + */ + public int getTolerance() { + return this.tolerance; + } + + /** + * Sets the error tolerance, which is the number of + * non-fatal errors (per unit) that should be tolerated before + * compilation is aborted. + */ + public void setTolerance(int tolerance) { + this.tolerance = tolerance; + } + + /** + * Gets the name of the base class for scripts. It must be a subclass + * of Script. + */ + public String getScriptBaseClass() { + return this.scriptBaseClass; + } + + /** + * Sets the name of the base class for scripts. It must be a subclass + * of Script. + */ + public void setScriptBaseClass(String scriptBaseClass) { + this.scriptBaseClass = scriptBaseClass; + } + + public ParserPluginFactory getPluginFactory() { + if (pluginFactory == null) { + pluginFactory = ParserVersion.V_2 == parserVersion + ? ParserPluginFactory.antlr2() + : ParserPluginFactory.antlr4(); + } + return pluginFactory; + } + + public void setPluginFactory(ParserPluginFactory pluginFactory) { + this.pluginFactory = pluginFactory; + } + + public void setScriptExtensions(Set<String> scriptExtensions) { + if(scriptExtensions == null) scriptExtensions = new LinkedHashSet<String>(); + this.scriptExtensions = scriptExtensions; + } + + public Set<String> getScriptExtensions() { + if(scriptExtensions == null || scriptExtensions.isEmpty()) { + /* + * this happens + * * when groovyc calls FileSystemCompiler in forked mode, or + * * when FileSystemCompiler is run from the command line directly, or + * * when groovy was not started using groovyc or FileSystemCompiler either + */ + scriptExtensions = SourceExtensionHandler.getRegisteredExtensions( + this.getClass().getClassLoader()); + } + return scriptExtensions; + } + + public String getDefaultScriptExtension() { + return defaultScriptExtension; + } + + + public void setDefaultScriptExtension(String defaultScriptExtension) { + this.defaultScriptExtension = defaultScriptExtension; + } + + public void setRecompileGroovySource(boolean recompile) { + recompileGroovySource = recompile; + } + + public boolean getRecompileGroovySource(){ + return recompileGroovySource; + } + + public void setMinimumRecompilationInterval(int time) { + minimumRecompilationInterval = Math.max(0,time); + } + + public int getMinimumRecompilationInterval() { + return minimumRecompilationInterval; + } + + /** + * Allow setting the bytecode compatibility. The parameter can take + * one of the values <tt>1.7</tt>, <tt>1.6</tt>, <tt>1.5</tt> or <tt>1.4</tt>. + * If wrong parameter then the value will default to VM determined version. + * + * @param version the bytecode compatibility mode + */ + public void setTargetBytecode(String version) { + for (String allowedJdk : ALLOWED_JDKS) { + if (allowedJdk.equals(version)) { + this.targetBytecode = version; + } + } + } + + /** + * Retrieves the compiler bytecode compatibility mode. + * + * @return bytecode compatibility mode. Can be either <tt>1.5</tt> or <tt>1.4</tt>. + */ + public String getTargetBytecode() { + return this.targetBytecode; + } + + private static String getVMVersion() { + return POST_JDK5; + } + + /** + * Gets the joint compilation options for this configuration. + * @return the options + */ + public Map<String, Object> getJointCompilationOptions() { + return jointCompilationOptions; + } + + /** + * Sets the joint compilation options for this configuration. + * Using null will disable joint compilation. + * @param options the options + */ + public void setJointCompilationOptions(Map<String, Object> options) { + jointCompilationOptions = options; + } + + /** + * Gets the optimization options for this configuration. + * @return the options (always not null) + */ + public Map<String, Boolean> getOptimizationOptions() { + return optimizationOptions; + } + + /** + * Sets the optimization options for this configuration. + * No entry or a true for that entry means to enable that optimization, + * a false means the optimization is disabled. + * Valid keys are "all" and "int". + * @param options the options. + * @throws IllegalArgumentException if the options are null + */ + public void setOptimizationOptions(Map<String, Boolean> options) { + if (options==null) throw new IllegalArgumentException("provided option map must not be null"); + optimizationOptions = options; + } + + /** + * Adds compilation customizers to the compilation process. A compilation customizer is a class node + * operation which performs various operations going from adding imports to access control. + * @param customizers the list of customizers to be added + * @return this configuration instance + */ + public CompilerConfiguration addCompilationCustomizers(CompilationCustomizer... customizers) { + if (customizers==null) throw new IllegalArgumentException("provided customizers list must not be null"); + compilationCustomizers.addAll(Arrays.asList(customizers)); + return this; + } + + /** + * Returns the list of compilation customizers. + * @return the customizers (always not null) + */ + public List<CompilationCustomizer> getCompilationCustomizers() { + return compilationCustomizers; + } + + /** + * Returns the list of disabled global AST transformation class names. + * @return a list of global AST transformation fully qualified class names + */ + public Set<String> getDisabledGlobalASTTransformations() { + return disabledGlobalASTTransformations; + } + + /** + * Disables global AST transformations. In order to avoid class loading side effects, it is not recommended + * to use MyASTTransformation.class.getName() by directly use the class name as a string. Disabled AST transformations + * only apply to automatically loaded global AST transformations, that is to say transformations defined in a + * META-INF/org.codehaus.groovy.transform.ASTTransformation file. If you explicitly add a global AST transformation + * in your compilation process, for example using the {@link org.codehaus.groovy.control.customizers.ASTTransformationCustomizer} or + * using a {@link org.codehaus.groovy.control.CompilationUnit.PrimaryClassNodeOperation}, then nothing will prevent + * the transformation from being loaded. + * @param disabledGlobalASTTransformations a set of fully qualified class names of global AST transformations + * which should not be loaded. + */ + public void setDisabledGlobalASTTransformations(final Set<String> disabledGlobalASTTransformations) { + this.disabledGlobalASTTransformations = disabledGlobalASTTransformations; + } + + public BytecodeProcessor getBytecodePostprocessor() { + return bytecodePostprocessor; + } + + public void setBytecodePostprocessor(final BytecodeProcessor bytecodePostprocessor) { + this.bytecodePostprocessor = bytecodePostprocessor; + } + + public ParserVersion getParserVersion() { + return this.parserVersion; + } + + public void setParserVersion(ParserVersion parserVersion) { + this.parserVersion = parserVersion; + } + + /** + * Check whether invoke dynamic enabled + * @return the result + */ + public boolean isIndyEnabled() { + Boolean indyEnabled = this.getOptimizationOptions().get(INVOKEDYNAMIC); + + if (null == indyEnabled) { + return false; + } + + return indyEnabled; + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/control/ConfigurationException.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/control/ConfigurationException.java b/src/main/java/org/codehaus/groovy/control/ConfigurationException.java new file mode 100644 index 0000000..3d0658a --- /dev/null +++ b/src/main/java/org/codehaus/groovy/control/ConfigurationException.java @@ -0,0 +1,92 @@ +/* + * 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 org.codehaus.groovy.GroovyExceptionInterface; + + + + +/** + * Thrown when configuration data is invalid. + * + * @author <a href="mailto:[email protected]">Chris Poirier</a> + */ + +public class ConfigurationException extends RuntimeException implements GroovyExceptionInterface +{ + + //--------------------------------------------------------------------------- + // CONSTRUCTION AND SUCH + + protected Exception cause; // The phase in which the failures occurred + + + /** + * Initializes the exception from a cause exception. + */ + + public ConfigurationException( Exception cause ) + { + super( cause.getMessage() ); + this.cause = cause; + } + + + /** + * Initializes the exception with just a message. + */ + + public ConfigurationException( String message ) + { + super( message ); + } + + + + /** + * Returns the causing exception, if available. + */ + + public Throwable getCause() + { + return cause; + } + + + /** + * Its always fatal. + */ + + public boolean isFatal() + { + return true; + } + + + + /** + * Set fatal is just ignored. + */ + + public void setFatal( boolean fatal ) + { + } + +}
