View Javadoc
1   /*-
2    * #%L
3    * io.earcam.instrumental.reflect
4    * %%
5    * Copyright (C) 2018 earcam
6    * %%
7    * SPDX-License-Identifier: (BSD-3-Clause OR EPL-1.0 OR Apache-2.0 OR MIT)
8    * 
9    * You <b>must</b> choose to accept, in full - any individual or combination of 
10   * the following licenses:
11   * <ul>
12   * 	<li><a href="https://opensource.org/licenses/BSD-3-Clause">BSD-3-Clause</a></li>
13   * 	<li><a href="https://www.eclipse.org/legal/epl-v10.html">EPL-1.0</a></li>
14   * 	<li><a href="https://www.apache.org/licenses/LICENSE-2.0">Apache-2.0</a></li>
15   * 	<li><a href="https://opensource.org/licenses/MIT">MIT</a></li>
16   * </ul>
17   * #L%
18   */
19  package io.earcam.instrumental.reflect;
20  
21  import static java.util.stream.Collectors.toList;
22  
23  import java.lang.invoke.MethodHandle;
24  import java.lang.invoke.MethodHandles;
25  import java.lang.invoke.MethodHandles.Lookup;
26  import java.lang.invoke.MethodType;
27  import java.lang.reflect.Field;
28  import java.lang.reflect.Method;
29  import java.lang.reflect.Modifier;
30  import java.lang.reflect.ParameterizedType;
31  import java.lang.reflect.Type;
32  import java.util.ArrayList;
33  import java.util.Arrays;
34  import java.util.List;
35  import java.util.Objects;
36  import java.util.Optional;
37  import java.util.function.Predicate;
38  import java.util.stream.Stream;
39  
40  import javax.annotation.ParametersAreNonnullByDefault;
41  
42  import io.earcam.unexceptional.Exceptional;
43  
44  /**
45   * <p>
46   * Methods class.
47   * </p>
48   *
49   */
50  @ParametersAreNonnullByDefault
51  public final class Methods {
52  
53  	/** Constant <code>IS_BRIDGE</code> */
54  	public static final Predicate<Method> IS_BRIDGE = Method::isBridge;
55  	/** Constant <code>IS_SYNTHETIC</code> */
56  	public static final Predicate<Method> IS_SYNTHETIC = Method::isSynthetic;
57  	/** Constant <code>IS_STATIC</code> */
58  	public static final Predicate<Method> IS_STATIC = hasModifiers(Modifier.STATIC);
59  
60  	static final Predicate<Method> IS_NOT_BRIDGE = IS_BRIDGE.negate();
61  	static final Predicate<Method> IS_NOT_SYNTHETIC = IS_SYNTHETIC.negate();
62  	static final Predicate<Method> IS_NOT_STATIC = IS_STATIC.negate();
63  
64  
65  	private Methods()
66  	{}
67  
68  
69  	/**
70  	 * <p>
71  	 * hasModifiers.
72  	 * </p>
73  	 *
74  	 * @param modifiers a int.
75  	 * @return a {@link java.util.function.Predicate} object.
76  	 */
77  	public static Predicate<Method> hasModifiers(int... modifiers)
78  	{
79  		int mods = Arrays.stream(modifiers).reduce(0, (a, b) -> a | b);
80  		return m -> (m.getModifiers() & mods) == mods;
81  	}
82  
83  
84  	/**
85  	 * <p>
86  	 * getMethod.
87  	 * </p>
88  	 *
89  	 * @param type a {@link java.lang.Class} object.
90  	 * @param name a {@link java.lang.String} object.
91  	 * @param parameterTypes a {@link java.lang.Class} object.
92  	 * @return a {@link java.util.Optional} object.
93  	 */
94  	public static Optional<Method> getMethod(Class<?> type, String name, Class<?>... parameterTypes)
95  	{
96  		return allMethodsOf(type)
97  				.filter(m -> m.getName().equals(name) && Arrays.equals(m.getParameterTypes(), parameterTypes))
98  				.findFirst();
99  	}
100 
101 
102 	/**
103 	 * Note: To find a <b>var-args</b> method, the <code>args</code> element must be an actual array
104 	 *
105 	 * @param instance a {@link java.lang.Object} object.
106 	 * @param name the method's name
107 	 * @param args instances from which the parameter type can be deduced
108 	 * @return the {@link java.lang.reflect.Method} if found
109 	 */
110 	public static Optional<Method> getMethod(Object instance, String name, Object... args)
111 	{
112 		return getMethod(instance.getClass(), name, parameterTypesOf(args));
113 	}
114 
115 
116 	private static Class<?>[] parameterTypesOf(Object[] args)
117 	{
118 		return Arrays.stream(args)
119 				.map(Object::getClass)
120 				.toArray(s -> new Class<?>[s]);
121 	}
122 
123 
124 	/**
125 	 * <p>
126 	 * allMethodsOf.
127 	 * </p>
128 	 *
129 	 * @param type the class
130 	 * @return absolutely <b>all</b> methods, including those inherited <b>and</b> those overridden
131 	 * @see #methodsOf(Class)
132 	 */
133 	public static Stream<Method> allMethodsOf(Class<?> type)
134 	{
135 		Objects.requireNonNull(type, "type cannot be null");
136 		Stream<Method> methods = Stream.empty();
137 		Class<?> t = type;
138 		while(t != null) {
139 			methods = Stream.concat(methods, Arrays.stream(t.getDeclaredMethods())).sequential();
140 			t = t.getSuperclass();
141 		}
142 		t = type;
143 		while(t != null) {
144 			for(Class<?> iface : t.getInterfaces()) {
145 				methods = Stream.concat(methods, Arrays.stream(iface.getDeclaredMethods()).filter(Method::isDefault)).sequential();
146 			}
147 			t = t.getSuperclass();
148 		}
149 		return methods;
150 	}
151 
152 
153 	/**
154 	 * <p>
155 	 * Finds <i>all</i> methods, including inherited but excluding overridden, bridge
156 	 * and synthetic - and excluding those inherited from {@link Object}.
157 	 * </p>
158 	 *
159 	 * @param type the class
160 	 * @return all methods, including inherited but excluding overridden, bridge and synthetic
161 	 * @see #allMethodsOf(Class)
162 	 */
163 	public static Stream<Method> methodsOf(Class<?> type)
164 	{
165 		List<Method> methods = allMethodsOf(type)
166 				.filter(IS_NOT_BRIDGE.and(IS_NOT_SYNTHETIC))
167 				.filter(Methods::isNotDeclaredOnObject)
168 				.collect(toList());
169 
170 		List<Method> overridenRidden = removeOverridden(methods);
171 		return overridenRidden.stream().sequential();
172 	}
173 
174 
175 	/**
176 	 * <p>
177 	 * removeOverridden.
178 	 * </p>
179 	 *
180 	 * @param methods a {@link java.util.List} object.
181 	 * @return a {@link java.util.List} object.
182 	 */
183 	protected static List<Method> removeOverridden(List<Method> methods)
184 	{
185 		List<Method> overridenRidden = new ArrayList<>(methods.size());
186 		for(Method method : methods) {
187 			if(!isOverridden(method, overridenRidden)) {
188 				overridenRidden.add(method);
189 			}
190 		}
191 		return overridenRidden;
192 	}
193 
194 
195 	private static boolean isNotDeclaredOnObject(Method m)
196 	{
197 		return m.getDeclaringClass() != Object.class;
198 	}
199 
200 
201 	private static boolean isOverridden(Method method, List<Method> overridenRidden)
202 	{
203 		Predicate<Method> bySameName = m -> m.getName().equals(method.getName());
204 		Predicate<Method> parameterTypes = sameParameters(method);
205 		Predicate<Method> returnTypes = m -> m.getReturnType().equals(method.getReturnType());
206 
207 		return overridenRidden.stream()
208 				.anyMatch(bySameName.and(parameterTypes).and(returnTypes));
209 	}
210 
211 
212 	private static Predicate<Method> sameParameters(Method method)
213 	{
214 		Predicate<Method> numberOfParameters = m -> m.getParameterCount() == method.getParameterCount();
215 		Predicate<Method> parameterTypes = m -> Arrays.equals(m.getParameterTypes(), method.getParameterTypes());
216 
217 		Predicate<Method> genericParameterTypes = m -> {
218 			Type[] genericTypes = m.getGenericParameterTypes();
219 			for(int i = 0; i < m.getParameterCount(); i++) {
220 				Class<?> type = getClassFromType(genericTypes[i], m.getParameterTypes()[i], m);
221 				if(!method.getParameterTypes()[i].isAssignableFrom(type)) {
222 					return false;
223 				}
224 			}
225 			return true;
226 		};
227 		return numberOfParameters.and(parameterTypes.or(genericParameterTypes));
228 	}
229 
230 
231 	private static Class<?> getClassFromType(Type type, Class<?> clazz, Method m)
232 	{
233 		if(type instanceof ParameterizedType) {
234 			ParameterizedType parameterized = (ParameterizedType) type;
235 			return Exceptional.apply(classLoaderOf(m)::loadClass, parameterized.getRawType().getTypeName());
236 		}
237 		if(type.getTypeName().equals(clazz.getCanonicalName())) {
238 			return clazz;
239 		}
240 		return Exceptional.apply(classLoaderOf(m)::loadClass, type.getTypeName());
241 	}
242 
243 
244 	private static ClassLoader classLoaderOf(Method m)
245 	{
246 		ClassLoader classLoader = m.getDeclaringClass().getClassLoader();
247 		return (classLoader == null) ? ClassLoader.getSystemClassLoader() : classLoader;
248 	}
249 
250 
251 	/**
252 	 * MethodHandle for normal class, or default interface, method.
253 	 *
254 	 * @param method a normal class instance or interface default method
255 	 * @return a handle
256 	 */
257 	public static MethodHandle handleFor(Method method)
258 	{
259 		return Exceptional.get(() -> isJava8() ? java8HandleFor(method) : java9PlusHandleFor(method));
260 	}
261 
262 
263 	private static boolean isJava8()
264 	{
265 		return System.getProperty("java.version").startsWith("1.8");
266 	}
267 
268 
269 	private static MethodHandle java9PlusHandleFor(Method method) throws ReflectiveOperationException
270 	{
271 		Class<?> targetClass = method.getDeclaringClass();
272 		String descriptor = Names.descriptorFor(method).toString();
273 		MethodType methodType = MethodType.fromMethodDescriptorString(descriptor, targetClass.getClassLoader());
274 
275 		if(IS_STATIC.test(method)) {
276 			return MethodHandles.lookup()
277 					.in(Methods.class)
278 					.findStatic(targetClass, method.getName(), methodType);
279 		} else if(method.isDefault()) {
280 			return MethodHandles.lookup()
281 					.in(Methods.class)
282 					.findSpecial(targetClass, method.getName(), methodType, targetClass);
283 		} else {
284 			return MethodHandles.lookup()
285 					.in(Methods.class)
286 					.findVirtual(targetClass, method.getName(), methodType);
287 		}
288 
289 	}
290 
291 
292 	private static MethodHandle java8HandleFor(Method method) throws ReflectiveOperationException
293 	{
294 		Class<?> targetClass = method.getDeclaringClass();
295 		Field field = Lookup.class.getDeclaredField("IMPL_LOOKUP");
296 		field.setAccessible(true);
297 		Lookup lookup = (Lookup) field.get(null);
298 		return (IS_STATIC.test(method)) ? lookup.unreflect(method) : lookup.unreflectSpecial(method, targetClass);
299 	}
300 }