You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by cc...@apache.org on 2017/12/17 14:02:01 UTC
[50/62] [abbrv] [partial] groovy git commit: Move Java source set
into `src/main/java`
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
+ }
+}