/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.codehaus.groovy.reflection.stdclasses; 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.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import groovy.lang.Closure; import groovy.util.ProxyGenerator; import org.codehaus.groovy.GroovyBugError; import org.codehaus.groovy.reflection.CachedClass; import org.codehaus.groovy.reflection.ClassInfo; import org.codehaus.groovy.reflection.ReflectionCache; import org.codehaus.groovy.runtime.ConvertedClosure; import org.codehaus.groovy.transform.trait.Traits; 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!"); } @Override public boolean isAssignableFrom(Class argument) { return argument == null || Closure.class.isAssignableFrom(argument) || ReflectionCache.isAssignableFrom(getTheClass(), argument); } @SuppressWarnings("unchecked") public static Object coerceToSAM(Closure argument, Method method, Class clazz, boolean isInterface) { 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)); } else { Map<String, Object> m = new HashMap<String,Object>(); m.put(method.getName(), argument); return ProxyGenerator.INSTANCE. instantiateAggregateFromBaseClass(m, clazz); } } @Override public Object coerceArgument(Object argument) { if (argument instanceof Closure) { Class clazz = getTheClass(); return coerceToSAM((Closure) argument, method, clazz, clazz.isInterface()); } else { return argument; } } private static Method[] getDeclaredMethods(final Class c) { try { Method[] methods = AccessController.doPrivileged(new PrivilegedAction<Method[]>() { public Method[] run() { return 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; getAbstractMethods(c.getSuperclass(), current); for (Class ci : c.getInterfaces()) { getAbstractMethods(ci, current); } for (Method m : getDeclaredMethods(c)) { if (Modifier.isPrivate(m.getModifiers())) continue; if (Modifier.isAbstract(m.getModifiers())) current.add(m); } } 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; return hasUsableImplementation(c.getSuperclass(), m); } private static Method getSingleNonDuplicateMethod(List<Method> current) { if (current.isEmpty()) return null; 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())) { continue; } return null; } return m; } /** * 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 */ 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; } return res; } else { LinkedList<Method> methods = new LinkedList(); getAbstractMethods(c, methods); if (methods.isEmpty()) return null; ListIterator<Method> it = methods.listIterator(); while (it.hasNext()) { Method m = it.next(); if (hasUsableImplementation(c, m)) it.remove(); } return getSingleNonDuplicateMethod(methods); } } }