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[]
+*/
+        '''
+    }
 }

Reply via email to