001package ball.lang.reflect; 002/*- 003 * ########################################################################## 004 * Utilities 005 * $Id: DefaultInvocationHandler.html 5431 2020-02-12 19:03:17Z ball $ 006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/hcf-dev/blog/2019-03-30-java-interface-facades/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.invoke.MethodHandles; 024import java.lang.reflect.Constructor; 025import java.lang.reflect.InvocationHandler; 026import java.lang.reflect.Method; 027import java.lang.reflect.Proxy; 028import java.util.ArrayList; 029import java.util.Arrays; 030import java.util.HashMap; 031import java.util.LinkedHashSet; 032import java.util.List; 033import java.util.Objects; 034import java.util.Set; 035import java.util.stream.Collectors; 036import java.util.stream.Stream; 037import lombok.NoArgsConstructor; 038import lombok.ToString; 039 040import static org.apache.commons.lang3.ClassUtils.getAllInterfaces; 041import static org.apache.commons.lang3.reflect.MethodUtils.invokeMethod; 042 043/** 044 * Default {@link InvocationHandler} implementation. 045 * See {@link #invoke(Object,Method,Object[])}. 046 * 047 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball} 048 * @version $Revision: 5431 $ 049 */ 050@NoArgsConstructor @ToString 051public class DefaultInvocationHandler implements InvocationHandler { 052 private final HashMap<Class<?>,List<Class<?>>> cache = new HashMap<>(); 053 054 /** 055 * See {@link Proxy#getProxyClass(ClassLoader,Class[])}. 056 * 057 * @param interfaces The interface {@link Class}es the 058 * {@link Proxy} {@link Class} will implement. 059 * 060 * @return The {@link Proxy}. 061 */ 062 public Class<?> getProxyClass(Class<?>... interfaces) throws IllegalArgumentException { 063 return Proxy.getProxyClass(getClass().getClassLoader(), interfaces); 064 } 065 066 /** 067 * See 068 * {@link Proxy#newProxyInstance(ClassLoader,Class[],InvocationHandler)}. 069 * Default implementation invokes {@link #getProxyClass(Class...)}. 070 * 071 * @param interfaces The interface {@link Class}es the 072 * {@link Proxy} {@link Class} will implement. 073 * 074 * @return The {@link Proxy}. 075 */ 076 public Object newProxyInstance(Class<?>... interfaces) throws IllegalArgumentException { 077 Object proxy = null; 078 079 try { 080 proxy = 081 getProxyClass(interfaces) 082 .getConstructor(InvocationHandler.class) 083 .newInstance(this); 084 } catch (IllegalArgumentException exception) { 085 throw exception; 086 } catch (RuntimeException exception) { 087 throw exception; 088 } catch (Exception exception) { 089 throw new IllegalStateException(exception); 090 } 091 092 return proxy; 093 } 094 095 /** 096 * {@inheritDoc} 097 * 098 * If the {@link Method#isDefault() method.isDefault()}, that 099 * {@link Method} will be invoked directly. If the {@link Method} is 100 * declared in {@link Object}, it is applied to {@link.this} 101 * {@link InvocationHandler}. Otherwise, the call will be dispatched to 102 * a declared {@link Method} on {@link.this} {@link InvocationHandler} 103 * with the same name and compatible parameter types (forcing access if 104 * necessary). 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, 111 Method method, Object[] argv) throws Throwable { 112 Object result = null; 113 Class<?> declarer = method.getDeclaringClass(); 114 115 if (method.isDefault()) { 116 Constructor<MethodHandles.Lookup> constructor = 117 MethodHandles.Lookup.class 118 .getDeclaredConstructor(Class.class); 119 120 constructor.setAccessible(true); 121 122 result = 123 constructor.newInstance(declarer) 124 .in(declarer) 125 .unreflectSpecial(method, declarer) 126 .bindTo(proxy) 127 .invokeWithArguments(argv); 128 } else if (declarer.equals(Object.class)) { 129 result = method.invoke(this, argv); 130 } else { 131 result = 132 invokeMethod(this, true, 133 method.getName(), 134 argv, method.getParameterTypes()); 135 } 136 137 return result; 138 } 139 140 /** 141 * Method available to subclass implementations to get the implemented 142 * interfaces of the argument {@link Class types}. The default 143 * implementation caches the results. 144 * 145 * @param type The {@link Class} to analyze. 146 * @param types Additional {@link Class}es to analyze. 147 * 148 * @return The {@link List} of interface {@link Class}es. 149 */ 150 protected List<Class<?>> getImplementedInterfacesOf(Class<?> type, 151 Class<?>... types) { 152 Set<Class<?>> set = 153 Stream.concat(Stream.of(type), Arrays.stream(types)) 154 .filter(Objects::nonNull) 155 .flatMap(t -> cache.computeIfAbsent(t, k -> compute(k)).stream()) 156 .collect(Collectors.toCollection(LinkedHashSet::new)); 157 158 return new ArrayList<>(set); 159 } 160 161 private List<Class<?>> compute(Class<?> type) { 162 Set<Class<?>> set = 163 Stream.concat(Stream.of(type), getAllInterfaces(type).stream()) 164 .filter(Class::isInterface) 165 .collect(Collectors.toCollection(LinkedHashSet::new)); 166 167 return new ArrayList<>(set); 168 } 169}