-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
CachedSAMClass.java
200 lines (182 loc) · 7.65 KB
/
CachedSAMClass.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
/*
* 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 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;
import org.codehaus.groovy.vmplugin.VMPluginFactory;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
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.Map;
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);
}
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())) {
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);
} else {
return argument;
}
}
@SuppressWarnings("removal") // TODO a future Groovy version should remove the security check
private static Method[] getDeclaredMethods(final Class c) {
try {
Method[] methods = VMPluginFactory.getPlugin().doPrivileged((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;
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;
methods.removeIf(m -> hasUsableImplementation(c, m));
return getSingleNonDuplicateMethod(methods);
}
}
}