You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by pa...@apache.org on 2016/06/25 06:16:31 UTC
[1/2] groovy git commit: GROOVY-7860: Groovy could implement an
@AutoImplement transform (closes #348)
Repository: groovy
Updated Branches:
refs/heads/master 9d94e2e4f -> 257619e7a
GROOVY-7860: Groovy could implement an @AutoImplement transform (closes #348)
Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/b79f43b5
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/b79f43b5
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/b79f43b5
Branch: refs/heads/master
Commit: b79f43b54481f7bbff83fe110c28801395525c49
Parents: 9d94e2e
Author: paulk <pa...@asert.com.au>
Authored: Sun Jun 12 21:23:29 2016 +1000
Committer: paulk <pa...@asert.com.au>
Committed: Sat Jun 25 16:16:06 2016 +1000
----------------------------------------------------------------------
src/main/groovy/transform/AutoImplement.java | 128 +++++++++++++
src/main/groovy/transform/Undefined.java | 4 +
.../groovy/antlr/AntlrParserPlugin.java | 46 +++--
.../AutoImplementASTTransformation.java | 182 +++++++++++++++++++
.../transform/AutoImplementTransformTest.groovy | 101 ++++++++++
5 files changed, 444 insertions(+), 17 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/groovy/blob/b79f43b5/src/main/groovy/transform/AutoImplement.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/transform/AutoImplement.java b/src/main/groovy/transform/AutoImplement.java
new file mode 100644
index 0000000..c41b887
--- /dev/null
+++ b/src/main/groovy/transform/AutoImplement.java
@@ -0,0 +1,128 @@
+/*
+ * 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 groovy.transform;
+
+import org.codehaus.groovy.transform.GroovyASTTransformationClass;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Class annotation used to provide default dummy methods for a class extending an abstract super class or
+ * implementing one or more interfaces.
+ * <p>
+ * Example usage:
+ * <pre>
+ * import groovy.transform.AutoImplement
+ *
+ * {@code @AutoImplement}
+ * class EmptyStringIterator implements Iterator<String> {
+ * boolean hasNext() { false }
+ * }
+ *
+ * assert !new EmptyStringIterator().hasNext()
+ * </pre>
+ * In the above example, since {@code hasNext} returns false, the {@code next} method
+ * should never be called, so any dummy implementation would do for {@code next}.
+ * The "empty" implementation provided by default when using {@code @AutoImplement}
+ * will suffice - which effectively returns {@code null} in Groovy for non-void,
+ * non-primitive methods.
+ *
+ * As a point of interest, the default implementation for methods returning primitive
+ * types is to return the default value (which incidentally never satisfies Groovy truth).
+ * For {@code boolean} this means returning {@code false}, so for the above example we
+ * could have (albeit perhaps less instructive of our intent) by just using:
+ * <pre>
+ * {@code @AutoImplement}
+ * class EmptyStringIterator implements Iterator<String> { }
+ * </pre>
+ * If we didn't want to assume that callers of our {@code EmptyStringIterator} correctly followed
+ * the {@code Iterator} contract, then we might want to guard against inappropriate calls to {@code next}.
+ * Rather than just returning {@code null}, we might want to throw an exception. This is easily done using
+ * the {@code exception} annotation attribute as shown below:
+ * <pre>
+ * import groovy.transform.AutoImplement
+ * import static groovy.test.GroovyAssert.shouldFail
+ *
+ * {@code @AutoImplement}(exception=UnsupportedOperationException)
+ * class EmptyStringIterator implements Iterator<String> {
+ * boolean hasNext() { false }
+ * }
+ *
+ * shouldFail(UnsupportedOperationException) {
+ * new EmptyStringIterator().next()
+ * }
+ * </pre>
+ * All implemented methods will throw an instance of this exception constructed using its no-arg constructor.
+ *
+ * You can also supply a single {@code message} annotation attribute in which case the message will be passed
+ * as an argument during exception construction as shown in the following example:
+ * <pre>
+ * {@code @AutoImplement}(exception=UnsupportedOperationException, message='Not supported for this empty iterator')
+ * class EmptyStringIterator implements Iterator<String> {
+ * boolean hasNext() { false }
+ * }
+ *
+ * def ex = shouldFail(UnsupportedOperationException) {
+ * new EmptyStringIterator().next()
+ * }
+ * assert ex.message == 'Not supported for this empty iterator'
+ * </pre>
+ * Finally, you can alternatively supply a {@code code} annotation attribute in which case a closure
+ * block can be supplied which should contain the code to execute for all implemented methods. This can be
+ * seen in the following example:
+ * <pre>
+ * {@code @AutoImplement}(code = { throw new UnsupportedOperationException("Not supported for ${getClass().simpleName}") })
+ * class EmptyStringIterator implements Iterator<String> {
+ * boolean hasNext() { false }
+ * }
+ *
+ * def ex = shouldFail(UnsupportedOperationException) {
+ * new EmptyStringIterator().next()
+ * }
+ * assert ex.message == 'Not supported for EmptyStringIterator'
+ * </pre>
+ *
+ * @since 2.5.0
+ */
+@java.lang.annotation.Documented
+@Retention(RetentionPolicy.SOURCE)
+@Target({ElementType.TYPE})
+@GroovyASTTransformationClass("org.codehaus.groovy.transform.AutoImplementASTTransformation")
+public @interface AutoImplement {
+ /**
+ * If defined, all unimplemented methods will throw this exception.
+ * Will be ignored if {@code code} is defined.
+ */
+ Class<? extends RuntimeException> exception() default Undefined.UNDEFINED_EXCEPTION.class;
+
+ /**
+ * If {@code exception} is defined, {@code message} can be used to specify the exception message.
+ * Will be ignored if {@code code} is defined or {@code exception} isn't defined.
+ */
+ String message() default Undefined.STRING;
+
+ /**
+ * If defined, all unimplemented methods will execute the code found within the supplied closure.
+ */
+ Class code() default Undefined.CLASS.class;
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/b79f43b5/src/main/groovy/transform/Undefined.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/transform/Undefined.java b/src/main/groovy/transform/Undefined.java
index e8f7873..e94424c 100644
--- a/src/main/groovy/transform/Undefined.java
+++ b/src/main/groovy/transform/Undefined.java
@@ -28,6 +28,10 @@ public final class Undefined {
private Undefined() {}
public static final String STRING = "<DummyUndefinedMarkerString-DoNotUse>";
public static final class CLASS {}
+ public static final class UNDEFINED_EXCEPTION extends RuntimeException {
+ private static final long serialVersionUID = -3960500360386581172L;
+ }
public static boolean isUndefined(String other) { return STRING.equals(other); }
public static boolean isUndefined(ClassNode other) { return CLASS.class.getName().equals(other.getName()); }
+ public static boolean isUndefinedException(ClassNode other) { return UNDEFINED_EXCEPTION.class.getName().equals(other.getName()); }
}
http://git-wip-us.apache.org/repos/asf/groovy/blob/b79f43b5/src/main/org/codehaus/groovy/antlr/AntlrParserPlugin.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/antlr/AntlrParserPlugin.java b/src/main/org/codehaus/groovy/antlr/AntlrParserPlugin.java
index 55a80ac..28a0395 100644
--- a/src/main/org/codehaus/groovy/antlr/AntlrParserPlugin.java
+++ b/src/main/org/codehaus/groovy/antlr/AntlrParserPlugin.java
@@ -1009,23 +1009,7 @@ public class AntlrParserPlugin extends ASTHelper implements ParserPlugin, Groovy
}
if (classNode.isInterface() && initialValue == null && type != null) {
- if (type == ClassHelper.int_TYPE) {
- initialValue = new ConstantExpression(0);
- } else if (type == ClassHelper.long_TYPE) {
- initialValue = new ConstantExpression(0L);
- } else if (type == ClassHelper.double_TYPE) {
- initialValue = new ConstantExpression(0.0);
- } else if (type == ClassHelper.float_TYPE) {
- initialValue = new ConstantExpression(0.0F);
- } else if (type == ClassHelper.boolean_TYPE) {
- initialValue = ConstantExpression.FALSE;
- } else if (type == ClassHelper.short_TYPE) {
- initialValue = new ConstantExpression((short) 0);
- } else if (type == ClassHelper.byte_TYPE) {
- initialValue = new ConstantExpression((byte) 0);
- } else if (type == ClassHelper.char_TYPE) {
- initialValue = new ConstantExpression((char) 0);
- }
+ initialValue = getDefaultValueForPrimitive(type);
}
@@ -1077,6 +1061,34 @@ public class AntlrParserPlugin extends ASTHelper implements ParserPlugin, Groovy
}
}
+ public static Expression getDefaultValueForPrimitive(ClassNode type) {
+ if (type == ClassHelper.int_TYPE) {
+ return new ConstantExpression(0);
+ }
+ if (type == ClassHelper.long_TYPE) {
+ return new ConstantExpression(0L);
+ }
+ if (type == ClassHelper.double_TYPE) {
+ return new ConstantExpression(0.0);
+ }
+ if (type == ClassHelper.float_TYPE) {
+ return new ConstantExpression(0.0F);
+ }
+ if (type == ClassHelper.boolean_TYPE) {
+ return ConstantExpression.FALSE;
+ }
+ if (type == ClassHelper.short_TYPE) {
+ return new ConstantExpression((short) 0);
+ }
+ if (type == ClassHelper.byte_TYPE) {
+ return new ConstantExpression((byte) 0);
+ }
+ if (type == ClassHelper.char_TYPE) {
+ return new ConstantExpression((char) 0);
+ }
+ return null;
+ }
+
protected ClassNode[] interfaces(AST node) {
List<ClassNode> interfaceList = new ArrayList<ClassNode>();
for (AST implementNode = node.getFirstChild(); implementNode != null; implementNode = implementNode.getNextSibling()) {
http://git-wip-us.apache.org/repos/asf/groovy/blob/b79f43b5/src/main/org/codehaus/groovy/transform/AutoImplementASTTransformation.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/transform/AutoImplementASTTransformation.java b/src/main/org/codehaus/groovy/transform/AutoImplementASTTransformation.java
new file mode 100644
index 0000000..b098d44
--- /dev/null
+++ b/src/main/org/codehaus/groovy/transform/AutoImplementASTTransformation.java
@@ -0,0 +1,182 @@
+/*
+ * 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.AutoImplement;
+import groovy.transform.Undefined;
+import org.codehaus.groovy.ast.ASTNode;
+import org.codehaus.groovy.ast.AnnotatedNode;
+import org.codehaus.groovy.ast.AnnotationNode;
+import org.codehaus.groovy.ast.ClassHelper;
+import org.codehaus.groovy.ast.ClassNode;
+import org.codehaus.groovy.ast.MethodNode;
+import org.codehaus.groovy.ast.Parameter;
+import org.codehaus.groovy.ast.expr.ClosureExpression;
+import org.codehaus.groovy.ast.expr.Expression;
+import org.codehaus.groovy.ast.stmt.BlockStatement;
+import org.codehaus.groovy.ast.stmt.EmptyStatement;
+import org.codehaus.groovy.ast.tools.ParameterUtils;
+import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.control.SourceUnit;
+import org.objectweb.asm.Opcodes;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.codehaus.groovy.antlr.AntlrParserPlugin.getDefaultValueForPrimitive;
+import static org.codehaus.groovy.ast.ClassHelper.make;
+import static org.codehaus.groovy.ast.expr.ArgumentListExpression.EMPTY_ARGUMENTS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.throwS;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.correctToGenericsSpec;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.correctToGenericsSpecRecurse;
+import static org.codehaus.groovy.ast.tools.GenericsUtils.createGenericsSpec;
+
+/**
+ * Handles generation of code for the @AutoImplement annotation.
+ */
+@GroovyASTTransformation(phase = CompilePhase.CANONICALIZATION)
+public class AutoImplementASTTransformation extends AbstractASTTransformation {
+ static final Class MY_CLASS = AutoImplement.class;
+ static final ClassNode MY_TYPE = make(MY_CLASS);
+ static final String MY_TYPE_NAME = "@" + MY_TYPE.getNameWithoutPackage();
+
+ public void visit(ASTNode[] nodes, SourceUnit source) {
+ init(nodes, source);
+ AnnotatedNode parent = (AnnotatedNode) nodes[1];
+ AnnotationNode anno = (AnnotationNode) nodes[0];
+ if (!MY_TYPE.equals(anno.getClassNode())) return;
+
+ if (parent instanceof ClassNode) {
+ ClassNode cNode = (ClassNode) parent;
+ if (!checkNotInterface(cNode, MY_TYPE_NAME)) return;
+ ClassNode exception = getMemberClassValue(anno, "exception");
+ if (exception != null && Undefined.isUndefinedException(exception)) {
+ exception = null;
+ }
+ String message = getMemberStringValue(anno, "message");
+ Expression code = anno.getMember("code");
+ if (code != null && !(code instanceof ClosureExpression)) {
+ addError("Expected closure value for annotation parameter 'code'. Found " + code, cNode);
+ return;
+ }
+ createMethods(cNode, exception, message, (ClosureExpression) code);
+ if (code != null) {
+ anno.setMember("code", new ClosureExpression(new Parameter[0], new EmptyStatement()));
+ }
+ }
+ }
+
+ private void createMethods(ClassNode cNode, ClassNode exception, String message, ClosureExpression code) {
+ for (MethodNode candidate : getAllCorrectedMethodsMap(cNode).values()) {
+ if (candidate.isAbstract()) {
+ cNode.addMethod(candidate.getName(), Opcodes.ACC_PUBLIC, candidate.getReturnType(),
+ candidate.getParameters(), candidate.getExceptions(),
+ methodBody(exception, message, code, candidate.getReturnType()));
+ }
+ }
+ }
+
+ /**
+ * Return all methods including abstract super/interface methods but only if not overridden
+ * by a concrete declared/inherited method.
+ */
+ private static Map<String, MethodNode> getAllCorrectedMethodsMap(ClassNode cNode) {
+ Map<String, MethodNode> result = new HashMap<String, MethodNode>();
+ for (MethodNode mn : cNode.getMethods()) {
+ result.put(mn.getTypeDescriptor(), mn);
+ }
+ ClassNode next = cNode;
+ while (true) {
+ Map<String, ClassNode> genericsSpec = createGenericsSpec(next);
+ for (MethodNode mn : next.getMethods()) {
+ MethodNode correctedMethod = correctToGenericsSpec(genericsSpec, mn);
+ if (next != cNode) {
+ ClassNode correctedClass = correctToGenericsSpecRecurse(genericsSpec, next);
+ MethodNode found = getDeclaredMethodCorrected(genericsSpec, correctedMethod, correctedClass);
+ if (found != null) {
+ String td = found.getTypeDescriptor();
+ if (result.containsKey(td) && !result.get(td).isAbstract()) {
+ continue;
+ }
+ result.put(td, found);
+ }
+ }
+ }
+ List<ClassNode> interfaces = new ArrayList<ClassNode>(Arrays.asList(next.getInterfaces()));
+ Map<String, ClassNode> updatedGenericsSpec = new HashMap<String, ClassNode>(genericsSpec);
+ while (!interfaces.isEmpty()) {
+ ClassNode origInterface = interfaces.remove(0);
+ if (!origInterface.equals(ClassHelper.OBJECT_TYPE)) {
+ updatedGenericsSpec = createGenericsSpec(origInterface, updatedGenericsSpec);
+ ClassNode correctedInterface = correctToGenericsSpecRecurse(updatedGenericsSpec, origInterface);
+ for (MethodNode nextMethod : correctedInterface.getMethods()) {
+ MethodNode correctedMethod = correctToGenericsSpec(genericsSpec, nextMethod);
+ MethodNode found = getDeclaredMethodCorrected(updatedGenericsSpec, correctedMethod, correctedInterface);
+ if (found != null) {
+ String td = found.getTypeDescriptor();
+ if (result.containsKey(td) && !result.get(td).isAbstract()) {
+ continue;
+ }
+ result.put(td, found);
+ }
+ }
+ interfaces.addAll(Arrays.asList(correctedInterface.getInterfaces()));
+ }
+ }
+ ClassNode superClass = next.getUnresolvedSuperClass();
+ if (superClass == null) {
+ break;
+ }
+ next = correctToGenericsSpecRecurse(updatedGenericsSpec, superClass);
+ }
+ return result;
+ }
+
+ private static MethodNode getDeclaredMethodCorrected(Map<String, ClassNode> genericsSpec, MethodNode origMethod, ClassNode correctedClass) {
+ for (MethodNode nameMatch : correctedClass.getDeclaredMethods(origMethod.getName())) {
+ MethodNode correctedMethod = correctToGenericsSpec(genericsSpec, nameMatch);
+ if (ParameterUtils.parametersEqual(correctedMethod.getParameters(), origMethod.getParameters())) {
+ return correctedMethod;
+ }
+ }
+ return null;
+ }
+
+ private BlockStatement methodBody(ClassNode exception, String message, ClosureExpression code, ClassNode returnType) {
+ BlockStatement body = new BlockStatement();
+ if (code != null) {
+ body.addStatement(code.getCode());
+ } else if (exception != null) {
+ body.addStatement(throwS(ctorX(exception, message == null ? EMPTY_ARGUMENTS : constX(message))));
+ } else {
+ Expression result = getDefaultValueForPrimitive(returnType);
+ if (result != null) {
+ body.addStatement(returnS(result));
+ }
+ }
+ return body;
+ }
+}
http://git-wip-us.apache.org/repos/asf/groovy/blob/b79f43b5/src/test/org/codehaus/groovy/transform/AutoImplementTransformTest.groovy
----------------------------------------------------------------------
diff --git a/src/test/org/codehaus/groovy/transform/AutoImplementTransformTest.groovy b/src/test/org/codehaus/groovy/transform/AutoImplementTransformTest.groovy
new file mode 100644
index 0000000..26fdc6f
--- /dev/null
+++ b/src/test/org/codehaus/groovy/transform/AutoImplementTransformTest.groovy
@@ -0,0 +1,101 @@
+/*
+ * 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
+
+class AutoImplementTransformTest extends GroovyShellTestCase {
+
+ void testGenericReturnTypes() {
+ assertScript '''
+ interface HasXs<T> {
+ T[] x()
+ }
+
+ abstract class HasXsY<E> implements HasXs<Long> {
+ abstract E y()
+ }
+
+ interface MyIt<T> extends Iterator<T> {}
+
+ @groovy.transform.AutoImplement
+ class Foo extends HasXsY<Integer> implements MyIt<String> { }
+
+ def publicMethods = Foo.methods.findAll{ it.modifiers == 1 }.collect{ "$it.returnType.simpleName $it.name" }*.toString()
+ assert ['boolean hasNext', 'String next', 'Long[] x', 'Integer y'].every{ publicMethods.contains(it) }
+ '''
+ }
+
+ void testException() {
+ shouldFail UnsupportedOperationException, '''
+ import groovy.transform.*
+
+ @AutoImplement(exception=UnsupportedOperationException)
+ class Foo implements Iterator<String> { }
+
+ new Foo().hasNext()
+ '''
+ }
+
+ void testExceptionWithMessage() {
+ def message = shouldFail UnsupportedOperationException, '''
+ import groovy.transform.*
+
+ @AutoImplement(exception=UnsupportedOperationException, message='Not supported by Foo')
+ class Foo implements Iterator<String> { }
+
+ new Foo().hasNext()
+ '''
+ assert message.contains('Not supported by Foo')
+ }
+
+ void testClosureBody() {
+ shouldFail IllegalStateException, '''
+ import groovy.transform.*
+
+ @AutoImplement(code={ throw new IllegalStateException()})
+ class Foo implements Iterator<String> { }
+
+ new Foo().hasNext()
+ '''
+ }
+
+ void testInheritedMethodNotOverwritten() {
+ assertScript '''
+ class WithNext {
+ String next() { 'foo' }
+ }
+
+ @groovy.transform.AutoImplement
+ class Foo extends WithNext implements Iterator<String> { }
+ assert new Foo().next() == 'foo'
+ '''
+ }
+
+ void testExistingMethodNotOverwritten() {
+ assertScript '''
+ @groovy.transform.AutoImplement
+ class Foo implements Iterator<String> {
+ String next() { 'foo' }
+ }
+
+ assert new Foo().next() == 'foo'
+ '''
+ }
+
+}
[2/2] groovy git commit: GROOVY-7860: Groovy could implement an
@AutoImplement transform (tweaks and additional doco)
Posted by pa...@apache.org.
GROOVY-7860: Groovy could implement an @AutoImplement transform (tweaks and additional doco)
Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/257619e7
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/257619e7
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/257619e7
Branch: refs/heads/master
Commit: 257619e7a6e2edb9a3fbf9d9e71e0c12b6575c5e
Parents: b79f43b
Author: paulk <pa...@asert.com.au>
Authored: Thu Jun 16 20:46:57 2016 +1000
Committer: paulk <pa...@asert.com.au>
Committed: Sat Jun 25 16:16:08 2016 +1000
----------------------------------------------------------------------
.../AutoImplementASTTransformation.java | 8 +-
src/spec/doc/core-metaprogramming.adoc | 112 +++++++++++++++
.../test/CodeGenerationASTTransformsTest.groovy | 140 +++++++++++++++++++
3 files changed, 257 insertions(+), 3 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/groovy/blob/257619e7/src/main/org/codehaus/groovy/transform/AutoImplementASTTransformation.java
----------------------------------------------------------------------
diff --git a/src/main/org/codehaus/groovy/transform/AutoImplementASTTransformation.java b/src/main/org/codehaus/groovy/transform/AutoImplementASTTransformation.java
index b098d44..6366627 100644
--- a/src/main/org/codehaus/groovy/transform/AutoImplementASTTransformation.java
+++ b/src/main/org/codehaus/groovy/transform/AutoImplementASTTransformation.java
@@ -34,6 +34,7 @@ import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.EmptyStatement;
import org.codehaus.groovy.ast.tools.ParameterUtils;
import org.codehaus.groovy.control.CompilePhase;
+import org.codehaus.groovy.control.GenericsVisitor;
import org.codehaus.groovy.control.SourceUnit;
import org.objectweb.asm.Opcodes;
@@ -48,6 +49,7 @@ import static org.codehaus.groovy.ast.ClassHelper.make;
import static org.codehaus.groovy.ast.expr.ArgumentListExpression.EMPTY_ARGUMENTS;
import static org.codehaus.groovy.ast.tools.GeneralUtils.constX;
import static org.codehaus.groovy.ast.tools.GeneralUtils.ctorX;
+import static org.codehaus.groovy.ast.tools.GeneralUtils.makeDescriptorWithoutReturnType;
import static org.codehaus.groovy.ast.tools.GeneralUtils.returnS;
import static org.codehaus.groovy.ast.tools.GeneralUtils.throwS;
import static org.codehaus.groovy.ast.tools.GenericsUtils.correctToGenericsSpec;
@@ -106,7 +108,7 @@ public class AutoImplementASTTransformation extends AbstractASTTransformation {
private static Map<String, MethodNode> getAllCorrectedMethodsMap(ClassNode cNode) {
Map<String, MethodNode> result = new HashMap<String, MethodNode>();
for (MethodNode mn : cNode.getMethods()) {
- result.put(mn.getTypeDescriptor(), mn);
+ result.put(makeDescriptorWithoutReturnType(mn), mn);
}
ClassNode next = cNode;
while (true) {
@@ -117,7 +119,7 @@ public class AutoImplementASTTransformation extends AbstractASTTransformation {
ClassNode correctedClass = correctToGenericsSpecRecurse(genericsSpec, next);
MethodNode found = getDeclaredMethodCorrected(genericsSpec, correctedMethod, correctedClass);
if (found != null) {
- String td = found.getTypeDescriptor();
+ String td = makeDescriptorWithoutReturnType(found);
if (result.containsKey(td) && !result.get(td).isAbstract()) {
continue;
}
@@ -136,7 +138,7 @@ public class AutoImplementASTTransformation extends AbstractASTTransformation {
MethodNode correctedMethod = correctToGenericsSpec(genericsSpec, nextMethod);
MethodNode found = getDeclaredMethodCorrected(updatedGenericsSpec, correctedMethod, correctedInterface);
if (found != null) {
- String td = found.getTypeDescriptor();
+ String td = makeDescriptorWithoutReturnType(found);
if (result.containsKey(td) && !result.get(td).isAbstract()) {
continue;
}
http://git-wip-us.apache.org/repos/asf/groovy/blob/257619e7/src/spec/doc/core-metaprogramming.adoc
----------------------------------------------------------------------
diff --git a/src/spec/doc/core-metaprogramming.adoc b/src/spec/doc/core-metaprogramming.adoc
index 2e0e47b..c76ae30 100644
--- a/src/spec/doc/core-metaprogramming.adoc
+++ b/src/spec/doc/core-metaprogramming.adoc
@@ -1429,6 +1429,118 @@ documentation.
The annotation attribute `forClass` is not supported for this strategy.
+[[xform-AutoImplement]]
+===== `@groovy.transform.AutoImplement`
+
+The `@AutoImplement` AST transformation supplies dummy implementations for any found abstract methods from
+superclasses or interfaces. The dummy implementation is the same for all abstract methods found and can be:
+
+* essentially empty (exactly true for void methods and for methods with a return type, returns the default value for
+that type)
+* a statement that throws a specified exception (with optional message)
+* some user supplied code
+
+The first example illustrates the default case. Our class is annotated with `@AutoImplement`,
+has a superclass and a single interface as can be seen here:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=autoimplement_default,indent=0]
+----
+
+A `void close()` method from the
+`Closeable` interface is supplied and left empty. Implementations are also supplied
+for the three abstract methods from the super class. The `get`, `addAll` and `size` methods
+have return types of `String`, `boolean` and `int` respectively with default values
+`null`, `false` and `0`. We can use our class (and check the expected return type for one
+of the methods) using the following code:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=autoimplement_default_usage,indent=0]
+----
+
+It is also worthwhile examining the equivalent generated code:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=autoimplement_default_equiv,indent=0]
+----
+
+The second example illustrates the simplest exception case. Our class is annotated with `@AutoImplement`,
+has a superclass and an annotation attribute indicates that an `IOException` should be thrown if any of
+our "dummy" methods are called. Here is the class definition:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=autoimplement_exception,indent=0]
+----
+
+We can use the class (and check the expected exception is thrown for one
+of the methods) using the following code:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=autoimplement_exception_usage,indent=0]
+----
+
+It is also worthwhile examining the equivalent generated code where three void methods
+have been provided all of which throw the supplied exception:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=autoimplement_exception_equiv,indent=0]
+----
+
+The third example illustrates the exception case with a supplied message. Our class is annotated with `@AutoImplement`,
+implements an interface, and has annotation attributes to indicate that an `UnsupportedOperationException` with
+`Not supported by MyIterator` as the message should be thrown for any supplied methods. Here is the class definition:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=autoimplement_exceptionmsg,indent=0]
+----
+
+We can use the class (and check the expected exception is thrown and has the correct message
+for one of the methods) using the following code:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=autoimplement_exceptionmsg_usage,indent=0]
+----
+
+It is also worthwhile examining the equivalent generated code where three void methods
+have been provided all of which throw the supplied exception:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=autoimplement_exceptionmsg_equiv,indent=0]
+----
+
+The fourth example illustrates the case of user supplied code. Our class is annotated with `@AutoImplement`,
+implements an interface, has an explcitly overriden `hasNext` method, and has an annotation attribute containing the
+supplied code for any supplied methods. Here is the class definition:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=autoimplement_code,indent=0]
+----
+
+We can use the class (and check the expected exception is thrown and has a message of the expected form)
+using the following code:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=autoimplement_code_usage,indent=0]
+----
+
+It is also worthwhile examining the equivalent generated code where the `next` method has been supplied:
+
+[source,groovy]
+----
+include::{projectdir}/src/spec/test/CodeGenerationASTTransformsTest.groovy[tags=autoimplement_code_equiv,indent=0]
+----
+
==== Class design annotations
This category of annotations are aimed at simplifying the implementation of well-known design patterns (delegation,
http://git-wip-us.apache.org/repos/asf/groovy/blob/257619e7/src/spec/test/CodeGenerationASTTransformsTest.groovy
----------------------------------------------------------------------
diff --git a/src/spec/test/CodeGenerationASTTransformsTest.groovy b/src/spec/test/CodeGenerationASTTransformsTest.groovy
index 0c70516..f6332ca 100644
--- a/src/spec/test/CodeGenerationASTTransformsTest.groovy
+++ b/src/spec/test/CodeGenerationASTTransformsTest.groovy
@@ -1395,4 +1395,144 @@ createFirstLastBorn()
// end::builder_initializer_immutable[]
'''
}
+
+ void testAutoImplement() {
+ assertScript '''
+// tag::autoimplement_default[]
+import groovy.transform.AutoImplement
+
+@AutoImplement
+class MyNames extends AbstractList<String> implements Closeable { }
+// end::autoimplement_default[]
+
+// tag::autoimplement_default_usage[]
+assert new MyNames().size() == 0
+// end::autoimplement_default_usage[]
+
+/*
+// tag::autoimplement_default_equiv[]
+class MyNames implements Closeable extends AbstractList<String> {
+
+ String get(int param0) {
+ return null
+ }
+
+ boolean addAll(Collection<? extends String> param0) {
+ return false
+ }
+
+ void close() throws Exception {
+ }
+
+ int size() {
+ return 0
+ }
+
+}
+// end::autoimplement_default_equiv[]
+*/
+ '''
+
+ assertScript '''
+import groovy.transform.AutoImplement
+// tag::autoimplement_exception[]
+@AutoImplement(exception=IOException)
+class MyWriter extends Writer { }
+// end::autoimplement_exception[]
+
+// tag::autoimplement_exception_usage[]
+import static groovy.test.GroovyAssert.shouldFail
+
+shouldFail(IOException) {
+ new MyWriter().flush()
+}
+// end::autoimplement_exception_usage[]
+
+/*
+// tag::autoimplement_exception_equiv[]
+class MyWriter extends Writer {
+
+ void flush() throws IOException {
+ throw new IOException()
+ }
+
+ void write(char[] param0, int param1, int param2) throws IOException {
+ throw new IOException()
+ }
+
+ void close() throws Exception {
+ throw new IOException()
+ }
+
+}
+// end::autoimplement_exception_equiv[]
+*/
+ '''
+
+ assertScript '''
+import groovy.transform.AutoImplement
+// tag::autoimplement_exceptionmsg[]
+@AutoImplement(exception=UnsupportedOperationException, message='Not supported by MyIterator')
+class MyIterator implements Iterator<String> { }
+// end::autoimplement_exceptionmsg[]
+
+import static groovy.test.GroovyAssert.shouldFail
+// tag::autoimplement_exceptionmsg_usage[]
+def ex = shouldFail(UnsupportedOperationException) {
+ new MyIterator().hasNext()
+}
+assert ex.message == 'Not supported by MyIterator'
+// end::autoimplement_exceptionmsg_usage[]
+
+/*
+// tag::autoimplement_exceptionmsg_equiv[]
+class MyIterator implements Iterator<String> {
+
+ boolean hasNext() {
+ throw new UnsupportedOperationException('Not supported by MyIterator')
+ }
+
+ String next() {
+ throw new UnsupportedOperationException('Not supported by MyIterator')
+ }
+
+}
+// end::autoimplement_exceptionmsg_equiv[]
+*/
+ '''
+
+ assertScript '''
+import groovy.transform.AutoImplement
+// tag::autoimplement_code[]
+@AutoImplement(code = { throw new UnsupportedOperationException('Should never be called but was called on ' + new Date()) })
+class EmptyIterator implements Iterator<String> {
+ boolean hasNext() { false }
+}
+// end::autoimplement_code[]
+
+import static groovy.test.GroovyAssert.shouldFail
+// tag::autoimplement_code_usage[]
+def ex = shouldFail(UnsupportedOperationException) {
+ new EmptyIterator().next()
+}
+assert ex.message.startsWith('Should never be called but was called on ')
+// end::autoimplement_code_usage[]
+
+/*
+// tag::autoimplement_code_equiv[]
+class EmptyIterator implements java.util.Iterator<String> {
+
+ boolean hasNext() {
+ false
+ }
+
+ String next() {
+ throw new UnsupportedOperationException('Should never be called but was called on ' + new Date())
+ }
+
+}
+// end::autoimplement_code_equiv[]
+*/
+ '''
+ }
}