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:32 UTC
[2/2] groovy git commit: GROOVY-7860: Groovy could implement an
@AutoImplement transform (tweaks and additional doco)
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[]
+*/
+ '''
+ }
}