http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/gradle/docs.gradle ---------------------------------------------------------------------- diff --git a/gradle/docs.gradle b/gradle/docs.gradle index e80dcd4..d10b9d5 100644 --- a/gradle/docs.gradle +++ b/gradle/docs.gradle @@ -44,7 +44,7 @@ def javadocSpec = { encoding = 'UTF-8' author = true version = true - overview = rootProject.file('src/main/overviewj.html') + overview = rootProject.file('src/main/java/overviewj.html') footer = doc.footer source = '1.8' links('http://docs.oracle.com/javase/8/docs/api/', 'http://docs.oracle.com/javaee/7/api/', @@ -55,13 +55,13 @@ def javadocSpec = { def groovydocSpec = { use = true - if (project != rootProject) source = project.sourceSets.main.allSource + source = project.sourceSets.main.allSource classpath = javadoc.classpath ext.windowtitle = doc.title ext.doctitle = doc.title header = doc.title footer = doc.footer - overviewText = rootProject.resources.text.fromFile('src/main/overview.html') + overviewText = rootProject.resources.text.fromFile('src/main/java/overview.html') includePrivate = false link 'http://docs.oracle.com/javaee/7/api/', 'javax.servlet.', 'javax.management.' link 'http://docs.oracle.com/javase/8/docs/api/', 'java.', 'org.xml.', 'javax.', 'org.w3c.'
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/gradle/test.gradle ---------------------------------------------------------------------- diff --git a/gradle/test.gradle b/gradle/test.gradle index df0ff06..b138af1 100644 --- a/gradle/test.gradle +++ b/gradle/test.gradle @@ -77,7 +77,7 @@ tasks.withType(Test) { if (testdb) { systemProperties 'groovy.testdb.props': testdb } - systemProperties 'apple.awt.UIElement': 'true', 'javadocAssertion.src.dir': './src/main' + systemProperties 'apple.awt.UIElement': 'true', 'javadocAssertion.src.dir': './src/main/java' systemProperties 'gradle.home': gradle.gradleHomeDir // this is needed by the security.policy classpath = files('src/test') + classpath http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/groovy/ASTTestTransformation.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/ASTTestTransformation.groovy b/src/main/groovy/ASTTestTransformation.groovy new file mode 100644 index 0000000..dcfe314 --- /dev/null +++ b/src/main/groovy/ASTTestTransformation.groovy @@ -0,0 +1,233 @@ +/* + * 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.transform + +import groovy.transform.CompilationUnitAware +import org.codehaus.groovy.ast.ASTNode +import org.codehaus.groovy.ast.AnnotationNode +import org.codehaus.groovy.ast.ClassCodeVisitorSupport +import org.codehaus.groovy.ast.ClassHelper +import org.codehaus.groovy.ast.ClassNode +import org.codehaus.groovy.ast.MethodNode +import org.codehaus.groovy.ast.expr.ClosureExpression +import org.codehaus.groovy.ast.expr.PropertyExpression +import org.codehaus.groovy.ast.expr.VariableExpression +import org.codehaus.groovy.ast.stmt.Statement +import org.codehaus.groovy.control.CompilationUnit +import org.codehaus.groovy.control.CompilePhase +import org.codehaus.groovy.control.CompilerConfiguration +import org.codehaus.groovy.control.ErrorCollector +import org.codehaus.groovy.control.Janitor +import org.codehaus.groovy.control.ProcessingUnit +import org.codehaus.groovy.control.SourceUnit +import org.codehaus.groovy.control.customizers.ImportCustomizer +import org.codehaus.groovy.control.io.ReaderSource +import org.codehaus.groovy.runtime.MethodClosure +import org.codehaus.groovy.syntax.SyntaxException +import org.codehaus.groovy.tools.Utilities + +import static org.codehaus.groovy.ast.tools.GeneralUtils.classX +import static org.codehaus.groovy.ast.tools.GeneralUtils.propX + +@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS) +class ASTTestTransformation extends AbstractASTTransformation implements CompilationUnitAware { + private CompilationUnit compilationUnit + + void visit(final ASTNode[] nodes, final SourceUnit source) { + AnnotationNode annotationNode = nodes[0] + def member = annotationNode.getMember('phase') + def phase = null + if (member) { + if (member instanceof VariableExpression) { + phase = CompilePhase.valueOf(member.text) + } else if (member instanceof PropertyExpression) { + phase = CompilePhase.valueOf(member.propertyAsString) + } + annotationNode.setMember('phase', propX(classX(ClassHelper.make(CompilePhase)), phase.toString())) + } + member = annotationNode.getMember('value') + if (member && !(member instanceof ClosureExpression)) { + throw new SyntaxException("ASTTest value must be a closure", member.getLineNumber(), member.getColumnNumber()) + } + if (!member && !annotationNode.getNodeMetaData(ASTTestTransformation)) { + throw new SyntaxException("Missing test expression", annotationNode.getLineNumber(), annotationNode.getColumnNumber()) + } + // convert value into node metadata so that the expression doesn't mix up with other AST xforms like type checking + annotationNode.putNodeMetaData(ASTTestTransformation, member) + annotationNode.getMembers().remove('value') + + def pcallback = compilationUnit.progressCallback + def callback = new CompilationUnit.ProgressCallback() { + Binding binding = new Binding([:].withDefault {null}) + + @Override + void call(final ProcessingUnit context, final int phaseRef) { + if (phase==null || phaseRef == phase.phaseNumber) { + ClosureExpression testClosure = nodes[0].getNodeMetaData(ASTTestTransformation) + StringBuilder sb = new StringBuilder() + for (int i = testClosure.lineNumber; i <= testClosure.lastLineNumber; i++) { + sb.append(source.source.getLine(i, new Janitor())).append('\n') + } + def testSource = sb.substring(testClosure.columnNumber + 1, sb.length()) + testSource = testSource.substring(0, testSource.lastIndexOf('}')) + CompilerConfiguration config = new CompilerConfiguration() + def customizer = new ImportCustomizer() + config.addCompilationCustomizers(customizer) + binding['sourceUnit'] = source + binding['node'] = nodes[1] + binding['lookup'] = new MethodClosure(LabelFinder, "lookup").curry(nodes[1]) + binding['compilationUnit'] = compilationUnit + binding['compilePhase'] = CompilePhase.fromPhaseNumber(phaseRef) + + GroovyShell shell = new GroovyShell(binding, config) + + source.AST.imports.each { + customizer.addImport(it.alias, it.type.name) + } + source.AST.starImports.each { + customizer.addStarImports(it.packageName) + } + source.AST.staticImports.each { + customizer.addStaticImport(it.value.alias, it.value.type.name, it.value.fieldName) + } + source.AST.staticStarImports.each { + customizer.addStaticStars(it.value.className) + } + shell.evaluate(testSource) + } + } + } + + if (pcallback!=null) { + if (pcallback instanceof ProgressCallbackChain) { + pcallback.addCallback(callback) + } else { + pcallback = new ProgressCallbackChain(pcallback, callback) + } + callback = pcallback + } + + compilationUnit.setProgressCallback(callback) + + } + + void setCompilationUnit(final CompilationUnit unit) { + this.compilationUnit = unit + } + + private static class AssertionSourceDelegatingSourceUnit extends SourceUnit { + private final ReaderSource delegate + + AssertionSourceDelegatingSourceUnit(final String name, final ReaderSource source, final CompilerConfiguration flags, final GroovyClassLoader loader, final ErrorCollector er) { + super(name, '', flags, loader, er) + delegate = source + } + + @Override + String getSample(final int line, final int column, final Janitor janitor) { + String sample = null; + String text = delegate.getLine(line, janitor); + + if (text != null) { + if (column > 0) { + String marker = Utilities.repeatString(" ", column - 1) + "^"; + + if (column > 40) { + int start = column - 30 - 1; + int end = (column + 10 > text.length() ? text.length() : column + 10 - 1); + sample = " " + text.substring(start, end) + Utilities.eol() + " " + + marker.substring(start, marker.length()); + } else { + sample = " " + text + Utilities.eol() + " " + marker; + } + } else { + sample = text; + } + } + + return sample; + + } + + } + + private static class ProgressCallbackChain extends CompilationUnit.ProgressCallback { + + private final List<CompilationUnit.ProgressCallback> chain = new LinkedList<CompilationUnit.ProgressCallback>() + + ProgressCallbackChain(CompilationUnit.ProgressCallback... callbacks) { + if (callbacks!=null) { + callbacks.each { addCallback(it) } + } + } + + public void addCallback(CompilationUnit.ProgressCallback callback) { + chain << callback + } + + @Override + void call(final ProcessingUnit context, final int phase) { + chain*.call(context, phase) + } + } + + public static class LabelFinder extends ClassCodeVisitorSupport { + + public static List<Statement> lookup(MethodNode node, String label) { + LabelFinder finder = new LabelFinder(label, null) + node.code.visit(finder) + + finder.targets + } + + public static List<Statement> lookup(ClassNode node, String label) { + LabelFinder finder = new LabelFinder(label, null) + node.methods*.code*.visit(finder) + node.declaredConstructors*.code*.visit(finder) + + finder.targets + } + + private final String label + private final SourceUnit unit + + private final List<Statement> targets = new LinkedList<Statement>(); + + LabelFinder(final String label, final SourceUnit unit) { + this.label = label + this.unit = unit; + } + + @Override + protected SourceUnit getSourceUnit() { + unit + } + + @Override + protected void visitStatement(final Statement statement) { + super.visitStatement(statement) + if (statement.statementLabel==label) targets << statement + } + + List<Statement> getTargets() { + return Collections.unmodifiableList(targets) + } + } + +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/groovy/ASTTransformationCustomizer.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/ASTTransformationCustomizer.groovy b/src/main/groovy/ASTTransformationCustomizer.groovy new file mode 100644 index 0000000..7e84968 --- /dev/null +++ b/src/main/groovy/ASTTransformationCustomizer.groovy @@ -0,0 +1,301 @@ +/* + * 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.customizers + +import groovy.transform.CompilationUnitAware +import org.codehaus.groovy.ast.ASTNode +import org.codehaus.groovy.ast.AnnotationNode +import org.codehaus.groovy.ast.ClassHelper +import org.codehaus.groovy.ast.ClassNode +import org.codehaus.groovy.ast.expr.ClassExpression +import org.codehaus.groovy.ast.expr.ClosureExpression +import org.codehaus.groovy.ast.expr.ConstantExpression +import org.codehaus.groovy.ast.expr.Expression +import org.codehaus.groovy.ast.expr.ListExpression +import org.codehaus.groovy.classgen.GeneratorContext +import org.codehaus.groovy.control.CompilationUnit +import org.codehaus.groovy.control.CompilePhase +import org.codehaus.groovy.control.SourceUnit +import org.codehaus.groovy.transform.ASTTransformation +import org.codehaus.groovy.transform.GroovyASTTransformation +import org.codehaus.groovy.transform.GroovyASTTransformationClass + +import java.lang.annotation.Annotation + +/** + * This customizer allows applying an AST transformation to a source unit with + * several strategies. + * + * Creating a customizer with the {@link ASTTransformationCustomizer#ASTTransformationCustomizer(Class) + * class constructor} will trigger an AST transformation for + * each class node of a source unit. However, you cannot pass parameters to the annotation so the default values + * will be used. Writing : + * <pre> + * def configuration = new CompilerConfiguration() + * configuration.addCompilationCustomizers(new ASTTransformationCustomizer(Log)) + * def shell = new GroovyShell(configuration) + * shell.evaluate(""" + * class MyClass { + * + * }""") + * </pre> + * + * is equivalent to : + * + * <pre> + * def shell = new GroovyShell() + * shell.evaluate(""" + * @Log + * class MyClass { + * + * }""") + * </pre> + * + * The class passed as a constructor parameter must be an AST transformation annotation. + * + * Alternatively, you can apply a global AST transformation by calling the + * {@link ASTTransformationCustomizer#ASTTransformationCustomizer(ASTTransformation) AST transformation + * constructor}. In that case, the transformation is applied once for the whole source unit. + * + * Unlike a global AST transformation declared in the META-INF/services/org.codehaus.groovy.transform.ASTTransformation + * file, which are applied if the file is in the classpath, using this customizer you'll have the choice to apply + * your transformation selectively. It can also be useful to debug global AST transformations without having to + * package your annotation in a jar file. + * + * @author Cedric Champeau + * + * @since 1.8.0 + * + */ +class ASTTransformationCustomizer extends CompilationCustomizer implements CompilationUnitAware { + private final AnnotationNode annotationNode; + final ASTTransformation transformation + + protected CompilationUnit compilationUnit; + private boolean applied = false; // used for global AST transformations + + /** + * Creates an AST transformation customizer using the specified annotation. The transformation classloader can + * be used if the transformation class cannot be loaded from the same class loader as the annotation class. + * It's assumed that the annotation is not annotated with {@code GroovyASTTransformationClass} and so the + * second argument supplies the link to the ASTTransformation class that should be used. + * @param transformationAnnotation + * @param astTransformationClassName + * @param transformationClassLoader + */ + ASTTransformationCustomizer(final Class<? extends Annotation> transformationAnnotation, String astTransformationClassName, ClassLoader transformationClassLoader) { + super(findPhase(transformationAnnotation, astTransformationClassName, transformationClassLoader)) + final Class<ASTTransformation> clazz = findASTTranformationClass(transformationAnnotation, astTransformationClassName, transformationClassLoader) + this.transformation = clazz.newInstance() + this.annotationNode = new AnnotationNode(ClassHelper.make(transformationAnnotation)) + } + + /** + * Creates an AST transformation customizer using the specified annotation. It's assumed that the annotation + * is not annotated with {@code GroovyASTTransformationClass} and so the second argument supplies the link to + * the ASTTransformation class that should be used. + * @param transformationAnnotation + * @param astTransformationClassName + */ + ASTTransformationCustomizer(final Class<? extends Annotation> transformationAnnotation, String astTransformationClassName) { + this(transformationAnnotation, astTransformationClassName, transformationAnnotation.classLoader) + } + + /** + * Creates an AST transformation customizer using the specified annotation. The transformation classloader can + * be used if the transformation class cannot be loaded from the same class loader as the annotation class. + * Additionally, you can pass a map of parameters that will be used to parameterize the annotation. + * It's assumed that the annotation is not annotated with {@code GroovyASTTransformationClass} and so the + * second argument supplies the link to the ASTTransformation class that should be used. + * @param transformationAnnotation + * @param astTransformationClassName + * @param transformationClassLoader + */ + ASTTransformationCustomizer(final Map annotationParams, final Class<? extends Annotation> transformationAnnotation, String astTransformationClassName, ClassLoader transformationClassLoader) { + super(findPhase(transformationAnnotation, astTransformationClassName, transformationClassLoader)) + final Class<ASTTransformation> clazz = findASTTranformationClass(transformationAnnotation, astTransformationClassName, transformationClassLoader) + this.transformation = clazz.newInstance() + this.annotationNode = new AnnotationNode(ClassHelper.make(transformationAnnotation)) + setAnnotationParameters(annotationParams) + } + + ASTTransformationCustomizer(final Map annotationParams, final Class<? extends Annotation> transformationAnnotation, String astTransformationClassName) { + this(annotationParams, transformationAnnotation, transformationAnnotation.classLoader) + } + + /** + * Creates an AST transformation customizer using the specified annotation. The transformation classloader can + * be used if the transformation class cannot be loaded from the same class loader as the annotation class. + * @param transformationAnnotation + * @param transformationClassLoader + */ + ASTTransformationCustomizer(final Class<? extends Annotation> transformationAnnotation, ClassLoader transformationClassLoader) { + super(findPhase(transformationAnnotation, transformationClassLoader)) + final Class<ASTTransformation> clazz = findASTTranformationClass(transformationAnnotation, transformationClassLoader) + this.transformation = clazz.newInstance() + this.annotationNode = new AnnotationNode(ClassHelper.make(transformationAnnotation)) + } + + /** + * Creates an AST transformation customizer using the specified annotation. + * @param transformationAnnotation + */ + ASTTransformationCustomizer(final Class<? extends Annotation> transformationAnnotation) { + this(transformationAnnotation, transformationAnnotation.classLoader) + } + + /** + * Creates an AST transformation customizer using the specified transformation. + */ + ASTTransformationCustomizer(final ASTTransformation transformation) { + super(findPhase(transformation)) + this.transformation = transformation + this.annotationNode = null + } + + /** + * Creates an AST transformation customizer using the specified annotation. The transformation classloader can + * be used if the transformation class cannot be loaded from the same class loader as the annotation class. + * Additionally, you can pass a map of parameters that will be used to parameterize the annotation. + * @param transformationAnnotation + * @param transformationClassLoader + */ + ASTTransformationCustomizer(final Map annotationParams, final Class<? extends Annotation> transformationAnnotation, ClassLoader transformationClassLoader) { + super(findPhase(transformationAnnotation, transformationClassLoader)) + final Class<ASTTransformation> clazz = findASTTranformationClass(transformationAnnotation, transformationClassLoader) + this.transformation = clazz.newInstance() + this.annotationNode = new AnnotationNode(ClassHelper.make(transformationAnnotation)) + setAnnotationParameters(annotationParams) + } + + ASTTransformationCustomizer(final Map annotationParams, final Class<? extends Annotation> transformationAnnotation) { + this(annotationParams, transformationAnnotation, transformationAnnotation.classLoader) + } + + ASTTransformationCustomizer(final Map annotationParams, final ASTTransformation transformation) { + this(transformation) + setAnnotationParameters(annotationParams) + } + + void setCompilationUnit(CompilationUnit unit) { + compilationUnit = unit + } + + private static Class<ASTTransformation> findASTTranformationClass(Class<? extends Annotation> anAnnotationClass, ClassLoader transformationClassLoader) { + final GroovyASTTransformationClass annotation = anAnnotationClass.getAnnotation(GroovyASTTransformationClass) + if (annotation==null) throw new IllegalArgumentException("Provided class doesn't look like an AST @interface") + + Class[] classes = annotation.classes() + String[] classesAsStrings = annotation.value() + if (classes.length+classesAsStrings.length>1) { + throw new IllegalArgumentException("AST transformation customizer doesn't support AST transforms with multiple classes") + } + return classes?classes[0]:Class.forName(classesAsStrings[0], true, transformationClassLoader?:anAnnotationClass.classLoader) + } + + private static Class<ASTTransformation> findASTTranformationClass(Class<? extends Annotation> anAnnotationClass, String astTransformationClassName, ClassLoader transformationClassLoader) { + return Class.forName(astTransformationClassName, true, transformationClassLoader?:anAnnotationClass.classLoader) as Class<ASTTransformation> + } + + private static CompilePhase findPhase(ASTTransformation transformation) { + if (transformation==null) throw new IllegalArgumentException("Provided transformation must not be null") + final Class<?> clazz = transformation.class + final GroovyASTTransformation annotation = clazz.getAnnotation(GroovyASTTransformation) + if (annotation==null) throw new IllegalArgumentException("Provided ast transformation is not annotated with "+GroovyASTTransformation.name) + + annotation.phase() + } + + private static CompilePhase findPhase(Class<? extends Annotation> annotationClass, ClassLoader transformationClassLoader) { + Class<ASTTransformation> clazz = findASTTranformationClass(annotationClass, transformationClassLoader); + + findPhase(clazz.newInstance()) + } + + private static CompilePhase findPhase(Class<? extends Annotation> annotationClass, String astTransformationClassName, ClassLoader transformationClassLoader) { + Class<ASTTransformation> clazz = findASTTranformationClass(annotationClass, astTransformationClassName, transformationClassLoader); + + findPhase(clazz.newInstance()) + } + + /** + * Specify annotation parameters. For example, if the annotation is : + * <pre>@Log(value='logger')</pre> + * You could create an AST transformation customizer and specify the "value" parameter thanks to this method: + * <pre>annotationParameters = [value: 'logger'] + * + * Note that you cannot specify annotation closure values directly. If the annotation you want to add takes + * a closure as an argument, you will have to set a {@link ClosureExpression} instead. This can be done by either + * creating a custom {@link ClosureExpression} from code, or using the {@link org.codehaus.groovy.ast.builder.AstBuilder}. + * + * Here is an example : + * <pre> + * // add @Contract({distance >= 0 }) + * customizer = new ASTTransformationCustomizer(Contract) + * final expression = new AstBuilder().buildFromCode(CompilePhase.CONVERSION) {-> + * distance >= 0 + * }.expression[0] + * customizer.annotationParameters = [value: expression]</pre> + * + * @param params the annotation parameters + * + * @since 1.8.1 + */ + public void setAnnotationParameters(Map<String,Object> params) { + if (params==null || annotationNode==null) return; + params.each { key, value -> + if (!annotationNode.classNode.getMethod(key)) { + throw new IllegalArgumentException("${annotationNode.classNode.name} does not accept any [$key] parameter") + } + if (value instanceof Closure) { + throw new IllegalArgumentException("Direct usage of closure is not supported by the AST " + + "compilation customizer. Please use ClosureExpression instead.") + } else if (value instanceof Expression) { + // avoid NPEs due to missing source code + value.setLineNumber(0) + value.setLastLineNumber(0) + annotationNode.addMember(key, value) + } else if (value instanceof Class) { + annotationNode.addMember(key, new ClassExpression(ClassHelper.make(value))) + } else if (value instanceof List) { + annotationNode.addMember(key, new ListExpression(value.collect { + it instanceof Class ? new ClassExpression(ClassHelper.make(it)) : new ConstantExpression(it) + })) + } else { + annotationNode.addMember(key, new ConstantExpression(value)) + } + } + } + + @Override + void call(SourceUnit source, GeneratorContext context, ClassNode classNode) { + if (transformation instanceof CompilationUnitAware) { + transformation.compilationUnit = compilationUnit + } + if (annotationNode!=null) { + // this is a local ast transformation which is applied on every class node + annotationNode.sourcePosition = classNode + transformation.visit([annotationNode, classNode] as ASTNode[], source) + } else { + // this is a global AST transformation + if (!applied) transformation.visit(null, source) + } + applied = true + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/groovy/ASTTransformationCustomizerFactory.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/ASTTransformationCustomizerFactory.groovy b/src/main/groovy/ASTTransformationCustomizerFactory.groovy new file mode 100644 index 0000000..4e4f5be --- /dev/null +++ b/src/main/groovy/ASTTransformationCustomizerFactory.groovy @@ -0,0 +1,60 @@ +/* + * 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.customizers.builder + +import groovy.transform.CompileStatic +import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer + +/** + * This factory generates an {@link ASTTransformationCustomizer ast transformation customizer}. + * <p> + * Simple syntax: + * <pre>builder.ast(ToString)</pre> + * With AST transformation options: + * <pre>builder.ast(includeNames:true, ToString)</pre> + * + * @author Cedric Champeau + * @since 2.1.0 + */ +class ASTTransformationCustomizerFactory extends AbstractFactory { + + @Override + @CompileStatic + public boolean isLeaf() { + true + } + + @Override + @CompileStatic + public boolean onHandleNodeAttributes(final FactoryBuilderSupport builder, final Object node, final Map attributes) { + false + } + + @Override + public Object newInstance(final FactoryBuilderSupport builder, final Object name, final Object value, final Map attributes) throws InstantiationException, IllegalAccessException { + ASTTransformationCustomizer customizer + if (attributes) { + customizer = new ASTTransformationCustomizer(attributes, value) + } else { + customizer = new ASTTransformationCustomizer(value) + } + customizer + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/groovy/AstBuilder.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/AstBuilder.groovy b/src/main/groovy/AstBuilder.groovy new file mode 100644 index 0000000..06d027b --- /dev/null +++ b/src/main/groovy/AstBuilder.groovy @@ -0,0 +1,145 @@ +/* + * 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.ast.builder + +import org.codehaus.groovy.ast.ASTNode +import org.codehaus.groovy.ast.stmt.BlockStatement +import org.codehaus.groovy.control.CompilePhase + +/** + * The AstBuilder provides several ways to build an abstract syntax tree (AST) of Groovy code. + * + * You can convert a String into AST using the buildFromString method. + * You can convert code into AST using the buildFromCode method. + * You can use the AST DSL with the buildFromSpec method. + * + * For more information, see the resources on the Groovy wiki pages. + * + * @author Hamlet D'Arcy + */ + +public class AstBuilder { + + /** + * Builds AST based on the code within the {@link Closure} parameter. + * + * This method <strong>must</strong> be invoked at compile time and never at runtime, because + * an ASTTransformation must be run to support it. If you receive an IllegalStateException then + * you most likely need to add stronger typing. For instance, this will not work: + * <code> + * def builder = new AstBuilder() + * builder.buildFromCode { + * // some code + * } + * </code> + * While this code will: + * <code> + * new AstBuilder().buildFromCode { + * // some code + * } + * </code> + * + * The compiler rewrites buildFromCode invocations into {@link AstBuilder#buildFromString(CompilePhase, boolean, String)} + * invocations. An exception raised during AST generation will show a stack trace from {@link AstBuilder#buildFromString(CompilePhase, boolean, String)} + * and not from {@link AstBuilder#buildFromCode(CompilePhase, boolean, Closure)} . + * + * The compiler saves the source code of the closure as a String within the Java class file. The String source + * of the closure will be visible and un-obfuscated within the class file. If your Closure parameter contains + * sensitive data such as a hard-coded password then that data is free to be seen by anyone with the class file. + * Do not store sensitive data within the closure parameter. + * + * @param phase + * the {@link CompilePhase} the AST will be targeted towards. Default is {@link CompilePhase#CLASS_GENERATION} + * @param statementsOnly + * when true, only the script statements are returned. WHen false, you will + * receive back a Script class node also. Default is true. + * @param block + * the code that will be converted + * @returns a List of {@link ASTNode} . + * @throws IllegalStateException + * this method may not be invoked at runtime. It works via a compile-time transformation + * of the closure source code into a String, which is sent to the {@link AstBuilder#buildFromString(CompilePhase, boolean, String)} + * method. The buildFromCode() method must be invoked against a strongly typed AstBuilder. + */ + List<ASTNode> buildFromCode(CompilePhase phase = CompilePhase.CLASS_GENERATION, boolean statementsOnly = true, Closure block) { + throw new IllegalStateException("""AstBuilder.build(CompilePhase, boolean, Closure):List<ASTNode> should never be called at runtime. +Are you sure you are using it correctly? +""") + } + + + /** + * Builds AST based on the code within the String parameter. + * + * @param phase + * the {@link CompilePhase} the AST will be targeted towards. Default is {@link CompilePhase#CLASS_GENERATION} + * @param statementsOnly + * when true, only the script statements are returned. WHen false, you will + * receive back a Script class node also. Default is true. + * @param source + * The source code String that will be compiled. + * @returns a List of {@link ASTNode} . + * @throws IllegalArgumentException + * if source is null or empty + */ + List<ASTNode> buildFromString(CompilePhase phase = CompilePhase.CLASS_GENERATION, boolean statementsOnly = true, String source) { + if (!source || "" == source.trim()) throw new IllegalArgumentException("A source must be specified") + return new AstStringCompiler().compile(source, phase, statementsOnly); + } + + /** + * Builds AST based on the code within the String parameter. The parameter is assumed to be + * a code block which is not legal Groovy code. A goto label is affixed to the block, compiled, + * and the resulting BlockStatement wrapper is removed before returning a result. + * @param phase + * the {@link CompilePhase} the AST will be targeted towards. Default is {@link CompilePhase#CLASS_GENERATION} + * @param statementsOnly + * when true, only the script statements are returned. WHen false, you will + * receive back a Script class node also. Default is true. + * @param source + * The source code String that will be compiled. The string must be a block wrapped in curly braces. + * @returns a List of {@link ASTNode} . + * @throws IllegalArgumentException + * if source is null or empty + */ + private List<ASTNode> buildFromBlock(CompilePhase phase = CompilePhase.CLASS_GENERATION, boolean statementsOnly = true, String source) { + if (!source || "" == source.trim()) throw new IllegalArgumentException("A source must be specified") + def labelledSource = "__synthesized__label__${System.currentTimeMillis()}__:" + source + List<ASTNode> result = new AstStringCompiler().compile(labelledSource, phase, statementsOnly) + // find the block statement from the result, and unwrap it from one level. + result.collect { node -> + if (node instanceof BlockStatement) { + ((BlockStatement)node).statements[0] //unwrap the artifact of pre-pending the goto label + } else { + node + } + } + } + + /** + * Builds AST based on the DSL data within the Closure parameter. + * @param specification + * the contents to create + */ + List<ASTNode> buildFromSpec(@DelegatesTo(AstSpecificationCompiler) Closure specification) { + if (specification == null) throw new IllegalArgumentException('Null: specification') + def properties = new AstSpecificationCompiler(specification) + return properties.expression + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/groovy/AstHelper.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/AstHelper.groovy b/src/main/groovy/AstHelper.groovy new file mode 100644 index 0000000..206b0df --- /dev/null +++ b/src/main/groovy/AstHelper.groovy @@ -0,0 +1,76 @@ +/* + * 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.transform.tailrec + +import groovy.transform.CompileStatic +import org.codehaus.groovy.ast.ClassNode +import org.codehaus.groovy.ast.expr.Expression +import org.codehaus.groovy.ast.expr.VariableExpression +import org.codehaus.groovy.ast.stmt.ContinueStatement +import org.codehaus.groovy.ast.stmt.ExpressionStatement +import org.codehaus.groovy.ast.stmt.Statement +import org.codehaus.groovy.ast.stmt.ThrowStatement + +import java.lang.reflect.Modifier + +import static org.codehaus.groovy.ast.tools.GeneralUtils.classX +import static org.codehaus.groovy.ast.tools.GeneralUtils.declS +import static org.codehaus.groovy.ast.tools.GeneralUtils.propX +import static org.codehaus.groovy.ast.tools.GeneralUtils.varX + +/** + * Helping to create a few standard AST constructs + * + * @author Johannes Link + */ +@CompileStatic +class AstHelper { + static ExpressionStatement createVariableDefinition(String variableName, ClassNode variableType, Expression value, boolean variableShouldBeFinal = false ) { + def newVariable = varX(variableName, variableType) + if (variableShouldBeFinal) + newVariable.setModifiers(Modifier.FINAL) + (ExpressionStatement) declS(newVariable, value) + } + + static ExpressionStatement createVariableAlias(String aliasName, ClassNode variableType, String variableName ) { + createVariableDefinition(aliasName, variableType, varX(variableName, variableType)) + } + + static VariableExpression createVariableReference(Map variableSpec) { + varX((String) variableSpec.name, (ClassNode) variableSpec.type) + } + + /** + * This statement should make the code jump to surrounding while loop's start label + * Does not work from within Closures + */ + static Statement recurStatement() { + //continue _RECUR_HERE_ + new ContinueStatement(InWhileLoopWrapper.LOOP_LABEL) + } + + /** + * This statement will throw exception which will be caught and redirected to jump to surrounding while loop's start label + * Also works from within Closures but is a tiny bit slower + */ + static Statement recurByThrowStatement() { + // throw InWhileLoopWrapper.LOOP_EXCEPTION + new ThrowStatement(propX(classX(InWhileLoopWrapper), 'LOOP_EXCEPTION')) + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/groovy/AstSpecificationCompiler.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/AstSpecificationCompiler.groovy b/src/main/groovy/AstSpecificationCompiler.groovy new file mode 100644 index 0000000..5e607b6 --- /dev/null +++ b/src/main/groovy/AstSpecificationCompiler.groovy @@ -0,0 +1,1080 @@ +/* + * 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.ast.builder + +import org.codehaus.groovy.ast.ASTNode +import org.codehaus.groovy.ast.AnnotationNode +import org.codehaus.groovy.ast.ClassHelper +import org.codehaus.groovy.ast.ClassNode +import org.codehaus.groovy.ast.ConstructorNode +import org.codehaus.groovy.ast.DynamicVariable +import org.codehaus.groovy.ast.FieldNode +import org.codehaus.groovy.ast.GenericsType +import org.codehaus.groovy.ast.ImportNode +import org.codehaus.groovy.ast.InnerClassNode +import org.codehaus.groovy.ast.MethodNode +import org.codehaus.groovy.ast.MixinNode +import org.codehaus.groovy.ast.Parameter +import org.codehaus.groovy.ast.PropertyNode +import org.codehaus.groovy.ast.VariableScope +import org.codehaus.groovy.ast.expr.AnnotationConstantExpression +import org.codehaus.groovy.ast.expr.ArgumentListExpression +import org.codehaus.groovy.ast.expr.ArrayExpression +import org.codehaus.groovy.ast.expr.AttributeExpression +import org.codehaus.groovy.ast.expr.BinaryExpression +import org.codehaus.groovy.ast.expr.BitwiseNegationExpression +import org.codehaus.groovy.ast.expr.BooleanExpression +import org.codehaus.groovy.ast.expr.CastExpression +import org.codehaus.groovy.ast.expr.ClassExpression +import org.codehaus.groovy.ast.expr.ClosureExpression +import org.codehaus.groovy.ast.expr.ClosureListExpression +import org.codehaus.groovy.ast.expr.ConstantExpression +import org.codehaus.groovy.ast.expr.ConstructorCallExpression +import org.codehaus.groovy.ast.expr.DeclarationExpression +import org.codehaus.groovy.ast.expr.ElvisOperatorExpression +import org.codehaus.groovy.ast.expr.Expression +import org.codehaus.groovy.ast.expr.FieldExpression +import org.codehaus.groovy.ast.expr.GStringExpression +import org.codehaus.groovy.ast.expr.ListExpression +import org.codehaus.groovy.ast.expr.MapEntryExpression +import org.codehaus.groovy.ast.expr.MapExpression +import org.codehaus.groovy.ast.expr.MethodCallExpression +import org.codehaus.groovy.ast.expr.MethodPointerExpression +import org.codehaus.groovy.ast.expr.NamedArgumentListExpression +import org.codehaus.groovy.ast.expr.NotExpression +import org.codehaus.groovy.ast.expr.PostfixExpression +import org.codehaus.groovy.ast.expr.PrefixExpression +import org.codehaus.groovy.ast.expr.PropertyExpression +import org.codehaus.groovy.ast.expr.RangeExpression +import org.codehaus.groovy.ast.expr.SpreadExpression +import org.codehaus.groovy.ast.expr.SpreadMapExpression +import org.codehaus.groovy.ast.expr.StaticMethodCallExpression +import org.codehaus.groovy.ast.expr.TernaryExpression +import org.codehaus.groovy.ast.expr.TupleExpression +import org.codehaus.groovy.ast.expr.UnaryMinusExpression +import org.codehaus.groovy.ast.expr.UnaryPlusExpression +import org.codehaus.groovy.ast.expr.VariableExpression +import org.codehaus.groovy.ast.stmt.AssertStatement +import org.codehaus.groovy.ast.stmt.BlockStatement +import org.codehaus.groovy.ast.stmt.BreakStatement +import org.codehaus.groovy.ast.stmt.CaseStatement +import org.codehaus.groovy.ast.stmt.CatchStatement +import org.codehaus.groovy.ast.stmt.ContinueStatement +import org.codehaus.groovy.ast.stmt.EmptyStatement +import org.codehaus.groovy.ast.stmt.ExpressionStatement +import org.codehaus.groovy.ast.stmt.ForStatement +import org.codehaus.groovy.ast.stmt.IfStatement +import org.codehaus.groovy.ast.stmt.ReturnStatement +import org.codehaus.groovy.ast.stmt.Statement +import org.codehaus.groovy.ast.stmt.SwitchStatement +import org.codehaus.groovy.ast.stmt.SynchronizedStatement +import org.codehaus.groovy.ast.stmt.ThrowStatement +import org.codehaus.groovy.ast.stmt.TryCatchStatement +import org.codehaus.groovy.ast.stmt.WhileStatement +import org.codehaus.groovy.runtime.MethodClosure +import org.codehaus.groovy.syntax.Token +import org.codehaus.groovy.syntax.Types + +/** + * Handles parsing the properties from the closure into values that can be referenced. + * + * This object is very stateful and not threadsafe. It accumulates expressions in the + * 'expression' field as they are found and executed within the DSL. + * + * Note: this class consists of many one-line method calls. A better implementation + * might be to take a declarative approach and replace the one-liners with map entries. + * + * @author Hamlet D'Arcy + */ +class AstSpecificationCompiler implements GroovyInterceptable { + + private final List<ASTNode> expression = [] + + /** + * Creates the DSL compiler. + */ + AstSpecificationCompiler(@DelegatesTo(AstSpecificationCompiler) Closure spec) { + spec.delegate = this + spec() + } + + /** + * Gets the current generated expression. + */ + List<ASTNode> getExpression() { + return expression + } + + /** + * This method takes a List of Classes (a "spec"), and makes sure that the expression field + * contains those classes. It is a safety mechanism to enforce that the DSL is being called + * properly. + * + * @param methodName + * the name of the method within the DSL that is being invoked. Used in creating error messages. + * @param spec + * the list of Class objects that the method expects to have in the expression field when invoked. + * @return + * the portions of the expression field that adhere to the spec. + */ + private List<ASTNode> enforceConstraints(String methodName, List<Class> spec) { + + // enforce that the correct # arguments was passed + if (spec.size() != expression.size()) { + throw new IllegalArgumentException("$methodName could not be invoked. Expected to receive parameters $spec but found ${expression?.collect { it.class }}") + } + + // enforce types and collect result + (0..(spec.size() - 1)).collect { int it -> + def actualClass = expression[it].class + def expectedClass = spec[it] + if (!expectedClass.isAssignableFrom(actualClass)) { + throw new IllegalArgumentException("$methodName could not be invoked. Expected to receive parameters $spec but found ${expression?.collect { it.class }}") + } + expression[it] + } + } + + /** + * This method helps you take Closure parameters to a method and bundle them into + * constructor calls to a specific ASTNode subtype. + * @param name + * name of object being constructed, used to create helpful error message. + * @param argBlock + * the actual parameters being specified for the node + * @param constructorStatement + * the type specific construction code that will be run + */ + private void captureAndCreateNode(String name, @DelegatesTo(AstSpecificationCompiler) Closure argBlock, Closure constructorStatement) { + if (!argBlock) throw new IllegalArgumentException("nodes of type $name require arguments to be specified") + + def oldProps = new ArrayList(expression) + expression.clear() + new AstSpecificationCompiler(argBlock) + def result = constructorStatement(expression) // invoke custom constructor for node + expression.clear() + expression.addAll(oldProps) + expression.add(result) + } + + /** + * Helper method to convert a DSL invocation into an ASTNode instance. + * + * @param target + * the class you are going to create + * @param typeAlias + * the DSL keyword that was used to invoke this type + * @param ctorArgs + * a specification of what arguments the constructor expects + * @param argBlock + * the single closure argument used during invocation + */ + private void makeNode(Class target, String typeAlias, List<Class<? super ASTNode>> ctorArgs, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode(target.class.simpleName, argBlock) { + target.newInstance(*enforceConstraints(typeAlias, ctorArgs)) + } + } + + /** + * Helper method to convert a DSL invocation with a list of parameters specified + * in a Closure into an ASTNode instance. + * + * @param target + * the class you are going to create + * @param argBlock + * the single closure argument used during invocation + */ + private void makeNodeFromList(Class target, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + //todo: add better error handling? + captureAndCreateNode(target.simpleName, argBlock) { + target.newInstance(new ArrayList(expression)) + } + } + + /** + * Helper method to convert a DSL invocation with a String parameter into a List of ASTNode instances. + * + * @param argBlock + * the single closure argument used during invocation + * @param input + * the single String argument used during invocation + */ + private void makeListOfNodes(@DelegatesTo(AstSpecificationCompiler) Closure argBlock, String input) { + captureAndCreateNode(input, argBlock) { + new ArrayList(expression) + } + } + + /** + * Helper method to convert a DSL invocation with a String parameter into an Array of ASTNode instances. + * + * @param argBlock + * the single closure argument used during invocation + * @param target + * the target type + */ + private void makeArrayOfNodes(Object target, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode(target.class.simpleName, argBlock) { + expression.toArray(target) + } + } + + /** + * Helper method to convert a DSL invocation into an ASTNode instance when a Class parameter is specified. + * + * @param target + * the class you are going to create + * @param alias + * the DSL keyword that was used to invoke this type + * @param spec + * the list of Classes that you expect to be present as parameters + * @param argBlock + * the single closure argument used during invocation + * @param type + * a type parameter + */ + private void makeNodeWithClassParameter(Class target, String alias, List<Class> spec, @DelegatesTo(AstSpecificationCompiler) Closure argBlock, Class type) { + captureAndCreateNode(target.class.simpleName, argBlock) { + expression.add(0, ClassHelper.make(type)) + target.newInstance(*enforceConstraints(alias, spec)) + } + } + + private void makeNodeWithStringParameter(Class target, String alias, List<Class> spec, @DelegatesTo(AstSpecificationCompiler) Closure argBlock, String text) { + captureAndCreateNode(target.class.simpleName, argBlock) { + expression.add(0, text) + target.newInstance(*enforceConstraints(alias, spec)) + } + } + + /** + * Creates a CastExpression. + */ + void cast(Class type, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNodeWithClassParameter(CastExpression, 'cast', [ClassNode, Expression], argBlock, type) + } + + /** + * Creates an ConstructorCallExpression. + */ + void constructorCall(Class type, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNodeWithClassParameter(ConstructorCallExpression, 'constructorCall', [ClassNode, Expression], argBlock, type) + } + + /** + * Creates a MethodCallExpression. + */ + void methodCall(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(MethodCallExpression, 'methodCall', [Expression, Expression, Expression], argBlock) + } + + /** + * Creates an AnnotationConstantExpression. + */ + void annotationConstant(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(AnnotationConstantExpression, 'annotationConstant', [AnnotationNode], argBlock) + } + + /** + * Creates a PostfixExpression. + */ + void postfix(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(PostfixExpression, 'postfix', [Expression, Token], argBlock) + } + + /** + * Creates a FieldExpression. + */ + void field(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(FieldExpression, 'field', [FieldNode], argBlock) + } + + /** + * Creates a MapExpression. + */ + void map(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNodeFromList(MapExpression, argBlock) + } + + /** + * Creates a TupleExpression. + */ + void tuple(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNodeFromList(TupleExpression, argBlock) + } + + /** + * Creates a MapEntryExpression. + */ + void mapEntry(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(MapEntryExpression, 'mapEntry', [Expression, Expression], argBlock) + } + + /** + * Creates a gString. + */ + void gString(String verbatimText, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNodeWithStringParameter(GStringExpression, 'gString', [String, List, List], argBlock, verbatimText) + } + + + /** + * Creates a methodPointer. + */ + + void methodPointer(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(MethodPointerExpression, 'methodPointer', [Expression, Expression], argBlock) + } + + /** + * Creates a property. + */ + void property(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(PropertyExpression, 'property', [Expression, Expression], argBlock) + } + + /** + * Creates a RangeExpression. + */ + void range(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(RangeExpression, 'range', [Expression, Expression, Boolean], argBlock) + } + + /** + * Creates EmptyStatement. + */ + void empty() { + expression << EmptyStatement.INSTANCE + } + + /** + * Creates a label. + */ + void label(String label) { + expression << label + } + + /** + * Creates an ImportNode. + */ + void importNode(Class target, String alias = null) { + expression << new ImportNode(ClassHelper.make(target), alias) + } + + /** + * Creates a CatchStatement. + */ + void catchStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(CatchStatement, 'catchStatement', [Parameter, Statement], argBlock) + } + + /** + * Creates a ThrowStatement. + */ + void throwStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(ThrowStatement, 'throwStatement', [Expression], argBlock) + } + + /** + * Creates a SynchronizedStatement. + */ + void synchronizedStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(SynchronizedStatement, 'synchronizedStatement', [Expression, Statement], argBlock) + } + + /** + * Creates a ReturnStatement. + */ + void returnStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(ReturnStatement, 'returnStatement', [Expression], argBlock) + } + + /** + * Creates a TernaryExpression. + */ + + private void ternary(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(TernaryExpression, 'ternary', [BooleanExpression, Expression, Expression], argBlock) + } + + + /** + * Creates an ElvisOperatorExpression. + */ + void elvisOperator(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(ElvisOperatorExpression, 'elvisOperator', [Expression, Expression], argBlock) + } + + /** + * Creates a BreakStatement. + */ + void breakStatement(String label = null) { + if (label) { + expression << new BreakStatement(label) + } else { + expression << new BreakStatement() + } + } + + /** + * Creates a ContinueStatement. + */ + void continueStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock = null) { + if (!argBlock) { + expression << new ContinueStatement() + } else { + makeNode(ContinueStatement, 'continueStatement', [String], argBlock) + } + } + + /** + * Create a CaseStatement. + */ + void caseStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(CaseStatement, 'caseStatement', [Expression, Statement], argBlock) + } + + /** + * Creates a BlockStatement. + */ + void defaultCase(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + block(argBlock) // same as arg block + } + + /** + * Creates a PrefixExpression. + */ + void prefix(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(PrefixExpression, 'prefix', [Token, Expression], argBlock) + } + + /** + * Creates a NotExpression. + */ + void not(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(NotExpression, 'not', [Expression], argBlock) + } + + /** + * Creates a DynamicVariable. + */ + void dynamicVariable(String variable, boolean isStatic = false) { + expression << new DynamicVariable(variable, isStatic) + } + + /** + * Creates a ClassNode[]. + */ + void exceptions(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeArrayOfNodes([] as ClassNode[], argBlock) + } + + /** + * Designates a list of AnnotationNodes. + */ + void annotations(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, "List<AnnotationNode>") + } + + + /** + * Designates a list of MethodNodes. + */ + void methods(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, "List<MethodNode>") + } + + /** + * Designates a list of ConstructorNodes. + */ + void constructors(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, "List<ConstructorNode>") + } + + /** + * Designates a list of {@code PropertyNode}s. + */ + void properties(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, "List<PropertyNode>") + } + + /** + * Designates a list of {@code FieldNode}s. + */ + void fields(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, "List<FieldNode>") + } + + /** + * Designates a list of ConstantExpressions. + */ + + void strings(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, "List<ConstantExpression>") + } + + /** + * Designates a list of Expressions. + */ + + void values(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, "List<Expression>") + } + + /** + * Creates a boolean value. + */ + void inclusive(boolean value) { + expression << value + } + + /** + * Creates a ConstantExpression. + */ + void constant(Object value) { + expression << new ConstantExpression(value) + } + + /** + * Creates an IfStatement. + */ + void ifStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(IfStatement, 'ifStatement', [BooleanExpression, Statement, Statement], argBlock) + } + + /** + * Creates a SpreadExpression. + */ + void spread(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(SpreadExpression, 'spread', [Expression], argBlock) + } + + /** + * Creates a SpreadMapExpression. + */ + void spreadMap(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(SpreadMapExpression, 'spreadMap', [Expression], argBlock) + } + + /** + * Creates a WhileStatement. + */ + void whileStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(WhileStatement, 'whileStatement', [BooleanExpression, Statement], argBlock) + } + + /** + * Create a ForStatement. + */ + void forStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(ForStatement, 'forStatement', [Parameter, Expression, Statement], argBlock) + } + + /** + * Creates a ClosureListExpression. + */ + void closureList(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNodeFromList(ClosureListExpression, argBlock) + } + + /** + * Creates a DeclarationExpression. + */ + void declaration(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(DeclarationExpression, 'declaration', [Expression, Token, Expression], argBlock) + } + + /** + * Creates a ListExpression. + */ + void list(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNodeFromList(ListExpression, argBlock) + } + + /** + * Creates a BitwiseNegationExpression. + */ + void bitwiseNegation(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(BitwiseNegationExpression, 'bitwiseNegation', [Expression], argBlock) + } + + /** + * Creates a ClosureExpression. + */ + void closure(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(ClosureExpression, 'closure', [Parameter[], Statement], argBlock) + } + + /** + * Creates a BooleanExpression. + */ + void booleanExpression(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(BooleanExpression, 'booleanExpression', [Expression], argBlock) + } + + /** + * Creates a BinaryExpression. + */ + void binary(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(BinaryExpression, 'binary', [Expression, Token, Expression], argBlock) + } + + /** + * Creates a UnaryPlusExpression. + */ + void unaryPlus(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(UnaryPlusExpression, 'unaryPlus', [Expression], argBlock) + } + + /** + * Creates a ClassExpression. + */ + void classExpression(Class type) { + expression << new ClassExpression(ClassHelper.make(type)) + } + + /** + * Creates a UnaryMinusExpression + */ + void unaryMinus(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(UnaryMinusExpression, 'unaryMinus', [Expression], argBlock) + } + + /** + * Creates an AttributeExpression. + */ + void attribute(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(AttributeExpression, 'attribute', [Expression, Expression], argBlock) + } + + /** + * Creates an ExpressionStatement. + */ + void expression(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNode(ExpressionStatement, 'expression', [Expression], argBlock) + } + + /** + * Creates a NamedArgumentListExpression. + */ + void namedArgumentList(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeNodeFromList(NamedArgumentListExpression, argBlock) + } + + /** + * Creates a ClassNode[]. + */ + void interfaces(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, "List<ClassNode>") + } + + /** + * Creates a MixinNode[]. + */ + void mixins(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, "List<MixinNode>") + } + + /** + * Creates a GenericsTypes[]. + */ + void genericsTypes(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, "List<GenericsTypes>") + } + + /** + * Creates a ClassNode. + */ + void classNode(Class target) { + expression << ClassHelper.make(target, false) + } + + /** + * Creates a Parameter[]. + */ + void parameters(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeArrayOfNodes([] as Parameter[], argBlock) + } + + /** + * Creates a BlockStatement. + */ + void block(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("BlockStatement", argBlock) { + return new BlockStatement(new ArrayList(expression), new VariableScope()) + } + } + + /** + * Creates a Parameter. + */ + void parameter(Map<String, Class> args, @DelegatesTo(AstSpecificationCompiler) Closure argBlock = null) { + if (!args) throw new IllegalArgumentException() + if (args.size() > 1) throw new IllegalArgumentException() + + //todo: add better error handling? + if (argBlock) { + args.each {name, type -> + captureAndCreateNode("Parameter", argBlock) { + new Parameter(ClassHelper.make(type), name, expression[0]) + } + } + } else { + args.each {name, type -> + expression << (new Parameter(ClassHelper.make(type), name)) + } + } + } + + /** + * Creates an ArrayExpression. + */ + void array(Class type, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("ArrayExpression", argBlock) { + new ArrayExpression(ClassHelper.make(type), new ArrayList(expression)) + } + } + + /** + * Creates a GenericsType. + */ + void genericsType(Class type, @DelegatesTo(AstSpecificationCompiler) Closure argBlock = null) { + if (argBlock) { + captureAndCreateNode("GenericsType", argBlock) { + new GenericsType(ClassHelper.make(type), expression[0] as ClassNode[], expression[1]) + } + } else { + expression << new GenericsType(ClassHelper.make(type)) + } + } + + /** + * Creates a list of upperBound ClassNodes. + */ + void upperBound(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + makeListOfNodes(argBlock, 'List<ClassNode>') + } + + /** + * Create lowerBound ClassNode. + */ + void lowerBound(Class target) { + expression << ClassHelper.make(target) + } + + /** + * Creates a 2 element list of name and Annotation. Used with Annotation Members. + */ + void member(String name, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("Annotation Member", argBlock) { + [name, expression[0]] + } + } + + /** + * Creates an ArgumentListExpression. + */ + void argumentList(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + if (!argBlock) { + expression << new ArgumentListExpression() + } else { + makeNodeFromList(ArgumentListExpression, argBlock) + } + } + + /** + * Creates an AnnotationNode. + */ + void annotation(Class target, @DelegatesTo(AstSpecificationCompiler) Closure argBlock = null) { + if (argBlock) { + //todo: add better error handling + captureAndCreateNode("ArgumentListExpression", argBlock) { + def node = new AnnotationNode(ClassHelper.make(target)) + expression?.each { + node.addMember(it[0], it[1]) + } + node + } + } else { + expression << new AnnotationNode(ClassHelper.make(target)) + } + } + + /** + * Creates a MixinNode. + */ + void mixin(String name, int modifiers, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("AttributeExpression", argBlock) { + if (expression.size() > 1) { + new MixinNode(name, modifiers, expression[0], new ArrayList(expression[1]) as ClassNode[]) + } else { + new MixinNode(name, modifiers, expression[0]) + } + } + } + + /** + * Creates a ClassNode + */ + void classNode(String name, int modifiers, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("ClassNode", argBlock) { + def result = new ClassNode(name, modifiers, + expression[0], + new ArrayList(expression[1]) as ClassNode[], + new ArrayList(expression[2]) as MixinNode[] + ) + while (expression.size() > 3) { + if (!List.isAssignableFrom(expression[3].getClass())) { + throw new IllegalArgumentException("Expecting to find list of additional items instead found: " + expression[3].getClass()) + } + if (expression[3].size() > 0) { + def clazz = expression[3][0].getClass() + switch(clazz) { + case GenericsType: + result.setGenericsTypes(new ArrayList(expression[3]) as GenericsType[]) + break + case MethodNode: + expression[3].each{ result.addMethod(it) } + break + case ConstructorNode: + expression[3].each{ result.addConstructor(it) } + break + case PropertyNode: + expression[3].each{ + it.field.owner = result + result.addProperty(it) + } + break + case FieldNode: + expression[3].each{ + it.owner = result + result.addField(it) + } + break + case AnnotationNode: + result.addAnnotations(new ArrayList(expression[3])) + break + default: + throw new IllegalArgumentException("Unexpected item found in ClassNode spec. Expecting [Field|Method|Property|Constructor|Annotation|GenericsType] but found: $clazz.name") + } + } + expression.remove(3) + } + result + } + } + + /** + * Creates an AssertStatement. + */ + void assertStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("AssertStatement", argBlock) { + if (expression.size() < 2) { + new AssertStatement(*enforceConstraints('assertStatement', [BooleanExpression])) + } else { + new AssertStatement(*enforceConstraints('assertStatement', [BooleanExpression, Expression])) + } + } + } + + /** + * Creates a TryCatchStatement. + */ + void tryCatch(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("TryCatchStatement", argBlock) { + def result = new TryCatchStatement(expression[0], expression[1]) + def catchStatements = expression.tail().tail() + catchStatements.each {statement -> result.addCatch(statement) } + return result + } + } + + /** + * Creates a VariableExpression. + */ + void variable(String variable) { + expression << new VariableExpression(variable) + } + + /** + * Creates a MethodNode. + */ + void method(String name, int modifiers, Class returnType, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("MethodNode", argBlock) { + //todo: enforce contract + def result = new MethodNode(name, modifiers, ClassHelper.make(returnType), expression[0], expression[1], expression[2]) + if (expression[3]) { + result.addAnnotations(new ArrayList(expression[3])) + } + result + } + } + + /** + * Creates a token. + */ + void token(String value) { + if (value == null) throw new IllegalArgumentException("Null: value") + + def tokenID = Types.lookupKeyword(value) + if (tokenID == Types.UNKNOWN) { + tokenID = Types.lookupSymbol(value) + } + if (tokenID == Types.UNKNOWN) throw new IllegalArgumentException("could not find token for $value") + + expression << new Token(tokenID, value, -1, -1) + } + + /** + * Creates a RangeExpression. + */ + void range(Range range) { + if (range == null) throw new IllegalArgumentException('Null: range') + expression << new RangeExpression(new ConstantExpression(range.getFrom()), new ConstantExpression(range.getTo()), true) //default is inclusive + } + + /** + * Creates a SwitchStatement. + */ + void switchStatement(@DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("SwitchStatement", argBlock) { + def switchExpression = expression.head() + def caseStatements = expression.tail().tail() + def defaultExpression = expression.tail().head() + new SwitchStatement(switchExpression, caseStatements, defaultExpression) + } + } + + /** + * Creates a mapEntry. + */ + void mapEntry(Map map) { + map.entrySet().each { + expression << new MapEntryExpression( + new ConstantExpression(it.key), + new ConstantExpression(it.value)) + } + } + + // + // todo: these methods can still be reduced smaller + // + + /** + * Creates a FieldNode. + */ + void fieldNode(String name, int modifiers, Class type, Class owner, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("FieldNode", argBlock) { + def annotations = null + if (expression.size() > 1) { + annotations = expression[1] + expression.remove(1) + } + expression.add(0, ClassHelper.make(owner)) + expression.add(0, ClassHelper.make(type)) + expression.add(0, modifiers) + expression.add(0, name) + def result = new FieldNode(*enforceConstraints('fieldNode', [String, Integer, ClassNode, ClassNode, Expression])) + if (annotations) { + result.addAnnotations(new ArrayList(annotations)) + } + result + } + } + + /** + * Creates an inner class. + */ + void innerClass(String name, int modifiers, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("InnerClassNode", argBlock) { + //todo: enforce contract + new InnerClassNode( + expression[0], + name, + modifiers, + expression[1], + new ArrayList(expression[2]) as ClassNode[], + new ArrayList(expression[3]) as MixinNode[]) + } + } + + /** + * Creates a PropertyNode. + */ + void propertyNode(String name, int modifiers, Class type, Class owner, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + //todo: improve error handling? + captureAndCreateNode("PropertyNode", argBlock) { + def annotations = null + // check if the last expression looks like annotations + if (List.isAssignableFrom(expression[-1].getClass())) { + annotations = expression[-1] + expression.remove(expression.size() - 1) + } + def result = new PropertyNode(name, modifiers, ClassHelper.make(type), ClassHelper.make(owner), + expression[0], // initial value (possibly null) + expression[1], // getter block (possibly null) + expression[2]) // setter block (possibly null) + if (annotations) { + result.addAnnotations(new ArrayList(annotations)) + } + result + } + } + + /** + * Creates a StaticMethodCallExpression. + */ + void staticMethodCall(Class target, String name, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("StaticMethodCallExpression", argBlock) { + expression.add(0, name) + expression.add(0, ClassHelper.make(target)) + new StaticMethodCallExpression(*enforceConstraints('staticMethodCall', [ClassNode, String, Expression])) + } + } + + /** + * Creates a StaticMethodCallExpression. + */ + void staticMethodCall(MethodClosure target, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("StaticMethodCallExpression", argBlock) { + expression.add(0, target.method) + expression.add(0, ClassHelper.makeWithoutCaching(target.owner.class, false)) + new StaticMethodCallExpression(*enforceConstraints('staticMethodCall', [ClassNode, String, Expression])) + } + } + + /** + * Creates a ConstructorNode. + */ + void constructor(int modifiers, @DelegatesTo(AstSpecificationCompiler) Closure argBlock) { + captureAndCreateNode("ConstructorNode", argBlock) { + def annotations = null + if (expression.size() > 3) { + annotations = expression[3] + expression.remove(3) + } + expression.add(0, modifiers) + def result = new ConstructorNode(*enforceConstraints('constructor', [Integer, Parameter[], ClassNode[], Statement])) + if (annotations) { + result.addAnnotations(new ArrayList(annotations)) + } + result + } + } +} http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/groovy/AstStringCompiler.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/AstStringCompiler.groovy b/src/main/groovy/AstStringCompiler.groovy new file mode 100644 index 0000000..497c125 --- /dev/null +++ b/src/main/groovy/AstStringCompiler.groovy @@ -0,0 +1,63 @@ +/* + * 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.ast.builder + +import groovy.transform.PackageScope +import org.codehaus.groovy.ast.ASTNode +import org.codehaus.groovy.ast.ModuleNode +import org.codehaus.groovy.control.CompilationUnit +import org.codehaus.groovy.control.CompilePhase +import org.codehaus.groovy.control.CompilerConfiguration + +/** + * This class handles converting Strings to ASTNode lists. + * + * @author Hamlet D'Arcy + */ +@PackageScope class AstStringCompiler { + + /** + * Performs the String source to {@link List} of {@link ASTNode}. + * + * @param script + * a Groovy script in String form + * @param compilePhase + * the int based CompilePhase to compile it to. + * @param statementsOnly + */ + List<ASTNode> compile(String script, CompilePhase compilePhase, boolean statementsOnly) { + def scriptClassName = "script" + System.currentTimeMillis() + GroovyClassLoader classLoader = new GroovyClassLoader() + GroovyCodeSource codeSource = new GroovyCodeSource(script, scriptClassName + ".groovy", "/groovy/script") + CompilationUnit cu = new CompilationUnit(CompilerConfiguration.DEFAULT, codeSource.codeSource, classLoader) + cu.addSource(codeSource.getName(), script); + cu.compile(compilePhase.getPhaseNumber()) + // collect all the ASTNodes into the result, possibly ignoring the script body if desired + return cu.ast.modules.inject([]) {List acc, ModuleNode node -> + if (node.statementBlock) acc.add(node.statementBlock) + node.classes?.each { + if (!(it.name == scriptClassName && statementsOnly)) { + acc << it + } + } + acc + } + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/groovy/CollectRecursiveCalls.groovy ---------------------------------------------------------------------- diff --git a/src/main/groovy/CollectRecursiveCalls.groovy b/src/main/groovy/CollectRecursiveCalls.groovy new file mode 100644 index 0000000..2c7e6de --- /dev/null +++ b/src/main/groovy/CollectRecursiveCalls.groovy @@ -0,0 +1,62 @@ +/* + * 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.transform.tailrec + +import groovy.transform.CompileStatic +import org.codehaus.groovy.ast.CodeVisitorSupport +import org.codehaus.groovy.ast.MethodNode +import org.codehaus.groovy.ast.expr.Expression +import org.codehaus.groovy.ast.expr.MethodCallExpression +import org.codehaus.groovy.ast.expr.StaticMethodCallExpression; + +/** + * Collect all recursive calls within method + * + * @author Johannes Link + */ +@CompileStatic +class CollectRecursiveCalls extends CodeVisitorSupport { + MethodNode method + List<Expression> recursiveCalls = [] + + public void visitMethodCallExpression(MethodCallExpression call) { + if (isRecursive(call)) { + recursiveCalls << call + } + super.visitMethodCallExpression(call) + } + + public void visitStaticMethodCallExpression(StaticMethodCallExpression call) { + if (isRecursive(call)) { + recursiveCalls << call + } + super.visitStaticMethodCallExpression(call) + } + + private boolean isRecursive(call) { + new RecursivenessTester().isRecursive(method: method, call: call) + } + + synchronized List<Expression> collect(MethodNode method) { + recursiveCalls.clear() + this.method = method + this.method.code.visit(this) + recursiveCalls + } +}
