This is an automated email from the ASF dual-hosted git repository. emilles pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push: new 9bce2df GROOVY-3015, GROOVY-4610: add GroovyInterceptable check to closure resolve strategy 9bce2df is described below commit 9bce2dff2068f6ee31c0681b30628987aab09a92 Author: Eric Milles <eric.mil...@thomsonreuters.com> AuthorDate: Tue May 18 08:41:04 2021 -0500 GROOVY-3015, GROOVY-4610: add GroovyInterceptable check to closure resolve strategy --- .../groovy/runtime/metaclass/ClosureMetaClass.java | 11 +++ .../runtime/metaclass/TransformMetaMethod.java | 16 +++- src/test/groovy/GroovyInterceptableTest.groovy | 106 ++++++++++++++++----- 3 files changed, 105 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 fa7fee1..b8ed7f4 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; @@ -207,6 +208,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 203591c..64a1eb6 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(); @@ -55,7 +55,17 @@ public class TransformMetaMethod extends MetaMethod { } @Override - public Object invoke(Object object, Object[] arguments) { + 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() + } }