001package ball.lang.reflect; 002/*- 003 * ########################################################################## 004 * Utilities 005 * $Id: DefaultInvocationHandler.html 6489 2020-07-19 17:22:55Z ball $ 006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/hcf-dev/blog/2020-07-19-java-multi-release-jars/src/main/resources/javadoc/src-html/ball/lang/reflect/DefaultInvocationHandler.html $ 007 * %% 008 * Copyright (C) 2008 - 2020 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: 6489 $ 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 * {@inheritDoc} 096 * 097 * If the {@link Method#isDefault() method.isDefault()}, that 098 * {@link Method} will be invoked directly. If the {@link Method} is 099 * declared in {@link Object}, it is applied to {@link.this} 100 * {@link InvocationHandler}. Otherwise, the call will be dispatched to 101 * a declared {@link Method} on {@link.this} {@link InvocationHandler} 102 * with the same name and compatible parameter types (forcing access if 103 * necessary). 104 * 105 * @throws Exception If no compatible {@link Method} is found or 106 * the {@link Method} cannot be invoked. 107 */ 108 @Override 109 public Object invoke(Object proxy, 110 Method method, Object[] argv) throws Throwable { 111 Object result = null; 112 Class<?> declarer = method.getDeclaringClass(); 113 114 if (method.isDefault()) { 115 result = 116 DefaultInterfaceMethodInvocationHandler.super 117 .invoke(proxy, method, argv); 118 } else if (declarer.equals(Object.class)) { 119 result = method.invoke(this, argv); 120 } else { 121 result = 122 invokeMethod(this, true, 123 method.getName(), 124 argv, method.getParameterTypes()); 125 } 126 127 return result; 128 } 129 130 /** 131 * Method available to subclass implementations to get the implemented 132 * interfaces of the argument {@link Class types}. The default 133 * implementation caches the results. 134 * 135 * @param type The {@link Class} to analyze. 136 * @param types Additional {@link Class}es to analyze. 137 * 138 * @return The {@link List} of interface {@link Class}es. 139 */ 140 protected List<Class<?>> getImplementedInterfacesOf(Class<?> type, 141 Class<?>... types) { 142 Set<Class<?>> set = 143 Stream.concat(Stream.of(type), Arrays.stream(types)) 144 .filter(Objects::nonNull) 145 .flatMap(t -> cache.computeIfAbsent(t, k -> compute(k)).stream()) 146 .collect(Collectors.toCollection(LinkedHashSet::new)); 147 148 return new ArrayList<>(set); 149 } 150 151 private List<Class<?>> compute(Class<?> type) { 152 Set<Class<?>> set = 153 Stream.concat(Stream.of(type), getAllInterfaces(type).stream()) 154 .filter(Class::isInterface) 155 .collect(Collectors.toCollection(LinkedHashSet::new)); 156 157 return new ArrayList<>(set); 158 } 159}