package tc.oc.commons.core.reflect; import java.lang.invoke.MethodHandle; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import com.google.common.cache.LoadingCache; import com.google.common.reflect.TypeToken; import tc.oc.commons.core.util.CacheUtils; import tc.oc.commons.core.util.Utils; public class PatternInvoker { private static class InvocationKey<T, R> { final TypeToken<T> target; final String name; final GenericMethodType<R> signature; private InvocationKey(TypeToken<T> target, String name, GenericMethodType<R> signature) { this.target = target; this.name = name; this.signature = signature; } @Override public int hashCode() { return Objects.hash(target, name, signature); } @Override public boolean equals(Object obj) { return Utils.equals(InvocationKey.class, this, obj, that -> this.target.equals(that.target) && this.name.equals(that.name) && this.signature.equals(that.signature) ); } Set<MethodHandle> methodHandles() { final MethodResolver resolver = new MethodResolver(target.getRawType()); return Methods.declaredMethodsInAncestors(target.getRawType()) .filter(method -> !Members.isStatic(method) && name.equals(method.getName()) && signature.canInvoke(target, method)) .map(method -> { try { return resolver.virtualHandle(target.getRawType(), method); } catch(NoSuchMethodException | IllegalAccessException e) { return null; } }) .collect(Collectors.toSet()); } } public static class InvocationHandle<T, R> { final InvocationKey<T, R> key; final Set<MethodHandle> methodHandles; InvocationHandle(InvocationKey<T, R> key) { this.key = key; this.methodHandles = key.methodHandles(); } public List<R> invoke(T target, List<Object> args) throws Throwable { if(methodHandles.isEmpty()) return Collections.emptyList(); final Object[] flatArgs = new Object[args.size() + 1]; flatArgs[0] = target; for(int i = 0; i < args.size(); i++) { flatArgs[i + 1] = args.get(i); } if(void.class.equals(key.signature.returnType().getRawType())) { for(MethodHandle handle : methodHandles) { handle.invokeWithArguments(flatArgs); } return Collections.emptyList(); } else { final List<R> results = new ArrayList<>(methodHandles.size()); for(MethodHandle handle : methodHandles) { results.add((R) handle.invokeWithArguments(flatArgs)); } return results; } } } private final LoadingCache<InvocationKey<?, ?>, InvocationHandle<?, ?>> invocations = CacheUtils.newCache(InvocationHandle::new); public <T, R> InvocationHandle<T, R> handle(InvocationKey<T, R> key) { return (InvocationHandle<T, R>) invocations.getUnchecked(key); } public <T, R> InvocationHandle<T, R> handle(TypeToken<T> target, String name, GenericMethodType<R> signature) { return handle(new InvocationKey<>(target, name, signature)); } public <T, R> InvocationHandle<T, R> handle(TypeToken<T> target, String name, TypeToken<R> returnType, List<TypeToken<?>> argumentTypes) { return handle(target, name, new GenericMethodType<>(returnType, argumentTypes)); } public <T, R> InvocationHandle<T, R> handle(Class<T> target, String name, Class<R> returnType, List<Class<?>> argumentTypes) { return handle(TypeToken.of(target), name, TypeToken.of(returnType), argumentTypes.stream().map(TypeToken::of).collect(Collectors.toList())); } public <T, R> List<R> invoke(TypeToken<T> targetType, String name, TypeToken<R> returnType, List<TypeToken<?>> argumentTypes, T target, List<Object> arguments) throws Throwable { return handle(targetType, name, returnType, argumentTypes).invoke(target, arguments); } }