001package ball.lang.reflect; 002/*- 003 * ########################################################################## 004 * Utilities 005 * %% 006 * Copyright (C) 2008 - 2022 Allen D. Ball 007 * %% 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 * ########################################################################## 020 */ 021import java.lang.reflect.InvocationHandler; 022import java.lang.reflect.Method; 023import java.lang.reflect.Proxy; 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.HashMap; 027import java.util.LinkedHashSet; 028import java.util.List; 029import java.util.Objects; 030import java.util.Set; 031import java.util.stream.Collectors; 032import java.util.stream.Stream; 033import lombok.NoArgsConstructor; 034import lombok.ToString; 035 036import static org.apache.commons.lang3.ClassUtils.getAllInterfaces; 037import static org.apache.commons.lang3.reflect.MethodUtils.invokeMethod; 038 039/** 040 * Default {@link InvocationHandler} implementation. 041 * See {@link #invoke(Object,Method,Object[])}. 042 * 043 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball} 044 */ 045@NoArgsConstructor @ToString 046public class DefaultInvocationHandler implements DefaultInterfaceMethodInvocationHandler { 047 private final HashMap<Class<?>,List<Class<?>>> cache = new HashMap<>(); 048 049 /** 050 * See {@link Proxy#getProxyClass(ClassLoader,Class[])}. 051 * 052 * @param interfaces The interface {@link Class}es the 053 * {@link Proxy} {@link Class} will implement. 054 * 055 * @return The {@link Proxy}. 056 */ 057 @SuppressWarnings({ "deprecation" }) 058 public Class<?> getProxyClass(Class<?>... interfaces) throws IllegalArgumentException { 059 return Proxy.getProxyClass(getClass().getClassLoader(), interfaces); 060 } 061 062 /** 063 * See 064 * {@link Proxy#newProxyInstance(ClassLoader,Class[],InvocationHandler)}. 065 * Default implementation invokes {@link #getProxyClass(Class...)}. 066 * 067 * @param interfaces The interface {@link Class}es the 068 * {@link Proxy} {@link Class} will implement. 069 * 070 * @return The {@link Proxy}. 071 */ 072 public Object newProxyInstance(Class<?>... interfaces) throws IllegalArgumentException { 073 Object proxy = null; 074 075 try { 076 proxy = 077 getProxyClass(interfaces) 078 .getConstructor(InvocationHandler.class) 079 .newInstance(this); 080 } catch (IllegalArgumentException exception) { 081 throw exception; 082 } catch (RuntimeException exception) { 083 throw exception; 084 } catch (Exception exception) { 085 throw new IllegalStateException(exception); 086 } 087 088 return proxy; 089 } 090 091 /** 092 * If the {@link Method#isDefault() method.isDefault()}, that 093 * {@link Method} will be invoked directly. If the {@link Method} is 094 * declared in {@link Object}, it is applied to {@link.this} 095 * {@link InvocationHandler}. Otherwise, the call will be dispatched to 096 * a declared {@link Method} on {@link.this} {@link InvocationHandler} 097 * with the same name and compatible parameter types (forcing access if 098 * necessary). 099 * 100 * @param proxy The proxy instance. 101 * @param method The {@link Method}. 102 * @param argv The argument array. 103 * 104 * @return The value to return from the {@link Method} invocation. 105 * 106 * @throws Exception If no compatible {@link Method} is found or 107 * the {@link Method} cannot be invoked. 108 */ 109 @Override 110 public Object invoke(Object proxy, Method method, Object[] argv) throws Throwable { 111 Object result = null; 112 Class<?> declarer = method.getDeclaringClass(); 113 114 if (method.isDefault()) { 115 result = DefaultInterfaceMethodInvocationHandler.super.invoke(proxy, method, argv); 116 } else if (declarer.equals(Object.class)) { 117 result = method.invoke(this, argv); 118 } else { 119 result = invokeMethod(this, true, method.getName(), argv, method.getParameterTypes()); 120 } 121 122 return result; 123 } 124 125 /** 126 * Method available to subclass implementations to get the implemented 127 * interfaces of the argument {@link Class types}. The default 128 * implementation caches the results. 129 * 130 * @param type The {@link Class} to analyze. 131 * @param types Additional {@link Class}es to analyze. 132 * 133 * @return The {@link List} of interface {@link Class}es. 134 */ 135 protected List<Class<?>> getImplementedInterfacesOf(Class<?> type, Class<?>... types) { 136 Set<Class<?>> set = 137 Stream.concat(Stream.of(type), Arrays.stream(types)) 138 .filter(Objects::nonNull) 139 .flatMap(t -> cache.computeIfAbsent(t, k -> compute(k)).stream()) 140 .collect(Collectors.toCollection(LinkedHashSet::new)); 141 142 return new ArrayList<>(set); 143 } 144 145 private List<Class<?>> compute(Class<?> type) { 146 Set<Class<?>> set = 147 Stream.concat(Stream.of(type), getAllInterfaces(type).stream()) 148 .filter(Class::isInterface) 149 .collect(Collectors.toCollection(LinkedHashSet::new)); 150 151 return new ArrayList<>(set); 152 } 153}