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 2022/10/12 14:57:50 UTC
[groovy] branch GROOVY_3_0_X updated: GROOVY-8243: support closure for functional interface that extends trait
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
The following commit(s) were added to refs/heads/GROOVY_3_0_X by this push:
new a1ddc30625 GROOVY-8243: support closure for functional interface that extends trait
a1ddc30625 is described below
commit a1ddc3062526e3762bdaca2b8fb52fdd06d5eaae
Author: Eric Milles <er...@thomsonreuters.com>
AuthorDate: Wed Oct 12 08:42:24 2022 -0500
GROOVY-8243: support closure for functional interface that extends trait
3_0_X backport
---
.../reflection/stdclasses/CachedSAMClass.java | 183 ++++++++++-----------
.../groovy/runtime/DefaultGroovyMethods.java | 10 +-
.../traitx/TraitASTTransformationTest.groovy | 8 +-
3 files changed, 94 insertions(+), 107 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/reflection/stdclasses/CachedSAMClass.java b/src/main/java/org/codehaus/groovy/reflection/stdclasses/CachedSAMClass.java
index 227eba4c7e..4acf6b4be1 100644
--- a/src/main/java/org/codehaus/groovy/reflection/stdclasses/CachedSAMClass.java
+++ b/src/main/java/org/codehaus/groovy/reflection/stdclasses/CachedSAMClass.java
@@ -30,90 +30,94 @@ import org.codehaus.groovy.transform.trait.Traits;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
+import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
-import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
public class CachedSAMClass extends CachedClass {
- private static final int ABSTRACT_STATIC_PRIVATE =
- Modifier.ABSTRACT|Modifier.PRIVATE|Modifier.STATIC;
- private static final int VISIBILITY = 5; // public|protected
- private static final Method[] EMPTY_METHOD_ARRAY = new Method[0];
private final Method method;
- public CachedSAMClass(Class klazz, ClassInfo classInfo) {
- super(klazz, classInfo);
- method = getSAMMethod(klazz);
- if (method==null) throw new GroovyBugError("assigned method should not have been null!");
+ public CachedSAMClass(Class clazz, ClassInfo classInfo) {
+ super(clazz, classInfo);
+ method = getSAMMethod(clazz);
+ if (method == null) throw new GroovyBugError("assigned method should not have been null!");
}
@Override
public boolean isAssignableFrom(Class argument) {
- return argument == null ||
- Closure.class.isAssignableFrom(argument) ||
- ReflectionCache.isAssignableFrom(getTheClass(), argument);
+ return argument == null
+ || Closure.class.isAssignableFrom(argument)
+ || ReflectionCache.isAssignableFrom(getTheClass(), argument);
}
+ @Override
+ public Object coerceArgument(Object argument) {
+ if (argument instanceof Closure) {
+ Class<?> clazz = getTheClass();
+ return coerceToSAM((Closure<?>) argument, method, clazz);
+ } else {
+ return argument;
+ }
+ }
+
+ //--------------------------------------------------------------------------
+
+ private static final Method[] EMPTY_METHOD_ARRAY = new Method[0];
+ private static final int PUBLIC_OR_PROTECTED = Modifier.PUBLIC | Modifier.PROTECTED;
+ private static final int ABSTRACT_STATIC_PRIVATE = Modifier.ABSTRACT | Modifier.STATIC | Modifier.PRIVATE;
+
public static Object coerceToSAM(Closure argument, Method method, Class clazz) {
return coerceToSAM(argument, method, clazz, clazz.isInterface());
}
- /* Should we make the following method private? */
- @SuppressWarnings("unchecked")
public static Object coerceToSAM(Closure argument, Method method, Class clazz, boolean isInterface) {
- if (argument!=null && clazz.isAssignableFrom(argument.getClass())) {
+ if (argument != null && clazz.isAssignableFrom(argument.getClass())) {
return argument;
}
- if (isInterface) {
- if (Traits.isTrait(clazz)) {
- Map<String,Closure> impl = Collections.singletonMap(
- method.getName(),
- argument
- );
- return ProxyGenerator.INSTANCE.instantiateAggregate(impl,Collections.singletonList(clazz));
- }
- return Proxy.newProxyInstance(
- clazz.getClassLoader(),
- new Class[]{clazz},
- new ConvertedClosure(argument));
+
+ if (!isInterface) {
+ return ProxyGenerator.INSTANCE.instantiateAggregateFromBaseClass(Collections.singletonMap(method.getName(), argument), clazz);
+ } else if (method != null && isOrImplementsTrait(clazz)) { // GROOVY-8243
+ return ProxyGenerator.INSTANCE.instantiateAggregate(Collections.singletonMap(method.getName(), argument), Collections.singletonList(clazz));
} else {
- Map<String, Object> m = new HashMap<String,Object>();
- m.put(method.getName(), argument);
- return ProxyGenerator.INSTANCE.
- instantiateAggregateFromBaseClass(m, clazz);
+ return Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new ConvertedClosure(argument));
}
}
-
- @Override
- public Object coerceArgument(Object argument) {
- if (argument instanceof Closure) {
- Class clazz = getTheClass();
- return coerceToSAM((Closure) argument, method, clazz);
- } else {
- return argument;
+
+ private static boolean isOrImplementsTrait(Class<?> c) {
+ if (Traits.isTrait(c)) return true; // quick check
+
+ Queue<Class<?>> todo = new ArrayDeque<>(Arrays.asList(c.getInterfaces()));
+ Set<Class<?>> done = new HashSet<>();
+ while ((c = todo.poll()) != null) {
+ if (done.add(c)) {
+ if (Traits.isTrait(c)) return true;
+ Collections.addAll(todo, c.getInterfaces());
+ }
}
+ return false;
}
- private static Method[] getDeclaredMethods(final Class c) {
+ private static Method[] getDeclaredMethods(final Class<?> c) {
try {
- Method[] methods = AccessController.doPrivileged((PrivilegedAction<Method[]>) c::getDeclaredMethods);
- if (methods!=null) return methods;
+ Method[] methods = java.security.AccessController.doPrivileged((java.security.PrivilegedAction<Method[]>) c::getDeclaredMethods);
+ if (methods != null) return methods;
} catch (java.security.AccessControlException ace) {
// swallow and do as if no method is available
}
return EMPTY_METHOD_ARRAY;
}
- private static void getAbstractMethods(Class c, List<Method> current) {
- if (c==null || !Modifier.isAbstract(c.getModifiers())) return;
+ private static void getAbstractMethods(Class<?> c, List<Method> current) {
+ if (c == null || !Modifier.isAbstract(c.getModifiers())) return;
getAbstractMethods(c.getSuperclass(), current);
- for (Class ci : c.getInterfaces()) {
+ for (Class<?> ci : c.getInterfaces()) {
getAbstractMethods(ci, current);
}
for (Method m : getDeclaredMethods(c)) {
@@ -122,16 +126,17 @@ public class CachedSAMClass extends CachedClass {
}
}
- private static boolean hasUsableImplementation(Class c, Method m) {
- if (c==m.getDeclaringClass()) return false;
+ private static boolean hasUsableImplementation(Class<?> c, Method m) {
+ if (c == m.getDeclaringClass()) return false;
Method found;
try {
found = c.getMethod(m.getName(), m.getParameterTypes());
int asp = found.getModifiers() & ABSTRACT_STATIC_PRIVATE;
- int visible = found.getModifiers() & VISIBILITY;
- if (visible !=0 && asp == 0) return true;
- } catch (NoSuchMethodException e) {/*ignore*/}
- if (c==Object.class) return false;
+ int visible = found.getModifiers() & PUBLIC_OR_PROTECTED;
+ if (visible != 0 && asp == 0) return true;
+ } catch (NoSuchMethodException ignore) {
+ }
+ if (c == Object.class) return false;
return hasUsableImplementation(c.getSuperclass(), m);
}
@@ -140,9 +145,7 @@ public class CachedSAMClass extends CachedClass {
if (current.size()==1) return current.get(0);
Method m = current.remove(0);
for (Method m2 : current) {
- if (m.getName().equals(m2.getName()) &&
- Arrays.equals(m.getParameterTypes(), m2.getParameterTypes()))
- {
+ if (m.getName().equals(m2.getName()) && Arrays.equals(m.getParameterTypes(), m2.getParameterTypes())) {
continue;
}
return null;
@@ -151,49 +154,39 @@ public class CachedSAMClass extends CachedClass {
}
/**
- * returns the abstract method from a SAM type, if it is a SAM type.
- * @param c the SAM class
- * @return null if nothing was found, the method otherwise
+ * Finds the abstract method of given class, if it is a SAM type.
*/
public static Method getSAMMethod(Class<?> c) {
- try {
- return getSAMMethodImpl(c);
- } catch (NoClassDefFoundError ignore) {
- return null;
- }
- }
-
- private static Method getSAMMethodImpl(Class<?> c) {
- // SAM = single public abstract method
// if the class is not abstract there is no abstract method
if (!Modifier.isAbstract(c.getModifiers())) return null;
- if (c.isInterface()) {
- Method[] methods = c.getMethods();
- // res stores the first found abstract method
- Method res = null;
- for (Method mi : methods) {
- // ignore methods, that are not abstract and from Object
- if (!Modifier.isAbstract(mi.getModifiers())) continue;
- // ignore trait methods which have a default implementation
- if (mi.getAnnotation(Traits.Implemented.class)!=null) continue;
- try {
- Object.class.getMethod(mi.getName(), mi.getParameterTypes());
- continue;
- } catch (NoSuchMethodException e) {/*ignore*/}
-
- // we have two methods, so no SAM
- if (res!=null) return null;
- res = mi;
+ try {
+ if (c.isInterface()) {
+ // res stores the first found abstract method
+ Method res = null;
+ for (Method mi : c.getMethods()) {
+ // ignore methods that are not abstract
+ if (!Modifier.isAbstract(mi.getModifiers())) continue;
+ // ignore trait methods with a default implementation
+ if (mi.getAnnotation(Traits.Implemented.class) != null) continue;
+ try { // ignore methods that are from java.lang.Object
+ Object.class.getMethod(mi.getName(), mi.getParameterTypes());
+ continue;
+ } catch (NoSuchMethodException ignore) {
+ }
+ // we have two methods, so no SAM
+ if (res != null) return null;
+ res = mi;
+ }
+ return res;
+ } else {
+ List<Method> methods = new LinkedList<>();
+ getAbstractMethods(c, methods);
+ if (methods.isEmpty()) return null;
+ methods.removeIf(m -> hasUsableImplementation(c, m));
+ return getSingleNonDuplicateMethod(methods);
}
- return res;
-
- } else {
-
- LinkedList<Method> methods = new LinkedList();
- getAbstractMethods(c, methods);
- if (methods.isEmpty()) return null;
- methods.removeIf(m -> hasUsableImplementation(c, m));
- return getSingleNonDuplicateMethod(methods);
+ } catch (NoClassDefFoundError ignore) {
+ return null;
}
}
}
diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
index d1e2346228..af167f167b 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
@@ -55,7 +55,6 @@ import groovy.util.OrderBy;
import groovy.util.PermutationGenerator;
import groovy.util.ProxyGenerator;
import org.apache.groovy.io.StringBuilderWriter;
-import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.classgen.Verifier;
import org.codehaus.groovy.reflection.ClassInfo;
import org.codehaus.groovy.reflection.MixinInMetaClass;
@@ -117,7 +116,6 @@ import java.io.Writer;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
-import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.math.BigDecimal;
@@ -12323,13 +12321,7 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport {
*/
public static <T> T asType(final Closure impl, final Class<T> type) {
if (type.isInterface() && !type.isInstance(impl)) {
- if (Traits.isTrait(type) || ClassHelper.make(type).getAllInterfaces().stream().anyMatch(Traits::isTrait)) { // GROOVY-8243
- Method sam = CachedSAMClass.getSAMMethod(type);
- if (sam != null) {
- return (T) ProxyGenerator.INSTANCE.instantiateAggregate(Collections.singletonMap(sam.getName(), impl), Collections.singletonList(type));
- }
- }
- return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, new ConvertedClosure(impl));
+ return (T) CachedSAMClass.coerceToSAM(impl, CachedSAMClass.getSAMMethod(type), type, true);
}
try {
diff --git a/src/test/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy b/src/test/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy
index 2c08caf11a..41f8282b98 100644
--- a/src/test/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy
+++ b/src/test/org/codehaus/groovy/transform/traitx/TraitASTTransformationTest.groovy
@@ -1568,13 +1568,15 @@ final class TraitASTTransformationTest {
void testSAMCoercion5() {
assertScript '''
trait T {
- abstract def foo(int i)
- def bar(double j) { "trait $j".toString() }
+ abstract foo(int n)
+ def bar(double n) {
+ "trait $n".toString()
+ }
}
interface I extends T {
}
- def obj = { "proxy $it".toString() } as I
+ I obj = { "proxy $it".toString() }
assert obj.foo(123) == 'proxy 123'
assert obj.bar(4.5) == 'trait 4.5'
'''