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