You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by em...@apache.org on 2021/08/17 18:18:22 UTC

[groovy] 01/04: GROOVY-3015, GROOVY-4610: add GroovyInterceptable check to closure resolve strategy

This is an automated email from the ASF dual-hosted git repository.

emilles pushed a commit to branch GROOVY_3_0_X
in repository https://gitbox.apache.org/repos/asf/groovy.git

commit 57482cd27a8d496ce381fb8932899ed7ef99c214
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Tue May 18 08:41:04 2021 -0500

    GROOVY-3015, GROOVY-4610: add GroovyInterceptable check to closure
    resolve strategy
    
    Conflicts:
    	src/main/java/org/codehaus/groovy/runtime/metaclass/TransformMetaMethod.java
---
 .../groovy/runtime/metaclass/ClosureMetaClass.java |  11 +++
 .../runtime/metaclass/TransformMetaMethod.java     |  17 +++-
 src/test/groovy/GroovyInterceptableTest.groovy     | 106 ++++++++++++++++-----
 3 files changed, 106 insertions(+), 28 deletions(-)

diff --git a/src/main/java/org/codehaus/groovy/runtime/metaclass/ClosureMetaClass.java b/src/main/java/org/codehaus/groovy/runtime/metaclass/ClosureMetaClass.java
index fafb786..e9e3b57 100644
--- a/src/main/java/org/codehaus/groovy/runtime/metaclass/ClosureMetaClass.java
+++ b/src/main/java/org/codehaus/groovy/runtime/metaclass/ClosureMetaClass.java
@@ -20,6 +20,7 @@ package org.codehaus.groovy.runtime.metaclass;
 
 import groovy.lang.Closure;
 import groovy.lang.ExpandoMetaClass;
+import groovy.lang.GroovyInterceptable;
 import groovy.lang.GroovyObject;
 import groovy.lang.GroovyRuntimeException;
 import groovy.lang.MetaBeanProperty;
@@ -204,6 +205,16 @@ public final class ClosureMetaClass extends MetaClassImpl {
                 if (method != null) return method;
             }
             return null;
+        } else if (delegate instanceof GroovyInterceptable) {
+            MetaClass delegateMetaClass = lookupObjectMetaClass(delegate);
+            // GROOVY-3015: must route calls through GroovyObject#invokeMethod(String,Object)
+            MetaMethod interceptMethod = delegateMetaClass.pickMethod("invokeMethod", new Class[]{String.class, Object.class});
+            return new TransformMetaMethod(interceptMethod) {
+                @Override
+                public Object invoke(final Object object, final Object[] arguments) {
+                    return super.invoke(object, new Object[]{methodName, arguments});
+                }
+            };
         } else {
             MetaClass delegateMetaClass = lookupObjectMetaClass(delegate);
             MetaMethod method = delegateMetaClass.pickMethod(methodName, argClasses);
diff --git a/src/main/java/org/codehaus/groovy/runtime/metaclass/TransformMetaMethod.java b/src/main/java/org/codehaus/groovy/runtime/metaclass/TransformMetaMethod.java
index 408c53e..be0b288 100644
--- a/src/main/java/org/codehaus/groovy/runtime/metaclass/TransformMetaMethod.java
+++ b/src/main/java/org/codehaus/groovy/runtime/metaclass/TransformMetaMethod.java
@@ -25,10 +25,10 @@ import org.codehaus.groovy.reflection.CachedClass;
  * A MetaMethod implementation useful for implementing coercion based invocations
  */
 public class TransformMetaMethod extends MetaMethod {
-    
+
     private final MetaMethod metaMethod;
 
-    public TransformMetaMethod(MetaMethod metaMethod) {
+    public TransformMetaMethod(final MetaMethod metaMethod) {
         this.metaMethod = metaMethod;
         setParametersTypes(metaMethod.getParameterTypes());
         nativeParamTypes = metaMethod.getNativeParameterTypes();
@@ -50,7 +50,18 @@ public class TransformMetaMethod extends MetaMethod {
         return metaMethod.getDeclaringClass();
     }
 
-    public Object invoke(Object object, Object[] arguments) {
+    @Override
+    public Object invoke(final Object object, final Object[] arguments) {
         return metaMethod.invoke(object, arguments);
     }
+
+    @Override
+    public Object doMethodInvoke(final Object object, final Object[] arguments) {
+        // no coerceArgumentsToClasses
+        try {
+            return invoke(object, arguments);
+        } catch (final Exception ex) {
+            throw processDoMethodInvokeException(ex, object, arguments);
+        }
+    }
 }
diff --git a/src/test/groovy/GroovyInterceptableTest.groovy b/src/test/groovy/GroovyInterceptableTest.groovy
index a667bc0..0053a1d 100644
--- a/src/test/groovy/GroovyInterceptableTest.groovy
+++ b/src/test/groovy/GroovyInterceptableTest.groovy
@@ -21,44 +21,101 @@ package groovy
 import groovy.test.GroovyTestCase
 import org.codehaus.groovy.runtime.ReflectionMethodInvoker
 
-class GroovyInterceptableTest extends GroovyTestCase {
+final class GroovyInterceptableTest extends GroovyTestCase {
 
-    void testMethodInterception() {
+    void testMethodIntercept1() {
         def g = new GI()
         assert g.someInt() == 2806
         assert g.someUnexistingMethod() == 1
         assert g.toString() == "invokeMethodToString"
     }
 
-    void testProperties() {
+    void testMethodIntercept2() {
         def g = new GI()
         assert g.foo == 89
         g.foo = 90
         assert g.foo == 90
-        // should this be 1 or 90?
+        // Should this be 1 or 90?
         assert g.getFoo() == 1
     }
-    
-    void testCallMissingMethod() {
+
+    // GROOVY-3015
+    void testMethodIntercept3() {
+        String shared = '''\
+            import org.codehaus.groovy.runtime.InvokerHelper
+            import org.codehaus.groovy.runtime.StringBufferWriter
+            import static groovy.test.GroovyTestCase.assertEquals
+
+            class Traceable implements GroovyInterceptable {
+                private static int indent = 1
+                Writer writer = new PrintWriter(System.out)
+                Object invokeMethod(String name, Object args) {
+                    writer.write('\\n' + ('  ' * indent) + 'Enter ' + name)
+                    indent += 1
+                    def result = InvokerHelper.getMetaClass(this).invokeMethod(this, name, args)
+                    indent -= 1
+                    writer.write('\\n' + ('  ' * indent) + 'Leave ' + name)
+                    return result
+                }
+            }
+
+            class Whatever extends Traceable {
+                int inner() { return 1 }
+                int outer() { return inner() }
+                int shouldTraceOuterAndInnerMethod() { return outer() }
+                def shouldTraceOuterAndInnerClosure = { -> return outer() }
+            }
+
+            def log = new StringBuffer()
+            def obj = new Whatever(writer: new StringBufferWriter(log))
+        '''
+
+        assertScript shared + '''
+            obj.shouldTraceOuterAndInnerMethod()
+
+            assertEquals """
+            |  Enter shouldTraceOuterAndInnerMethod
+            |    Enter outer
+            |      Enter inner
+            |      Leave inner
+            |    Leave outer
+            |  Leave shouldTraceOuterAndInnerMethod""".stripMargin(), log.toString()
+        '''
+
+        assertScript shared + '''
+            obj.shouldTraceOuterAndInnerClosure()
+
+            assertEquals """
+            |  Enter shouldTraceOuterAndInnerClosure
+            |    Enter outer
+            |      Enter inner
+            |      Leave inner
+            |    Leave outer
+            |  Leave shouldTraceOuterAndInnerClosure""".stripMargin(), log.toString()
+        '''
+    }
+
+    void testMissingMethod1() {
         def obj = new GI2()
         shouldFail { obj.notAMethod() }
-        assert 'missing' == obj.result 
+        assert 'missing' == obj.result
     }
- 
-    void testCallMissingMethodFromInstance() {
+
+    void testMissingMethod2() {
         def obj = new GI2()
         shouldFail { obj.method() }
         assert 'missing' == obj.result
-   }
+    }
 }
 
-class GI implements GroovyInterceptable {
+//------------------------------------------------------------------------------
 
+class GI implements GroovyInterceptable {
     def foo = 89
-
     int someInt() { 2806 }
+    @Override
     String toString() { "originalToString" }
-
+    @Override
     Object invokeMethod(String name, Object args) {
         if ("toString" == name)
             return "invokeMethodToString"
@@ -69,17 +126,16 @@ class GI implements GroovyInterceptable {
     }
 }
 
-
 class GI2 implements GroovyInterceptable {
-  def result = ""
-  def invokeMethod(String name, args) {
-    def metaMethod = Foo.metaClass.getMetaMethod(name, args)
-    if (metaMethod != null) return metaMethod.invoke(this, args)
-    result += "missing"
-    throw new MissingMethodException(name, Foo.class, args)
-  }
-  
-  def method() {
-      notAMethod()
-  }
+    def result = ""
+    @Override
+    def invokeMethod(String name, args) {
+        def metaMethod = Foo.metaClass.getMetaMethod(name, args)
+        if (metaMethod != null) return metaMethod.invoke(this, args)
+        result += "missing"
+        throw new MissingMethodException(name, Foo.class, args)
+    }
+    def method() {
+        notAMethod()
+    }
 }