001package ball.lang.reflect; 002/*- 003 * ########################################################################## 004 * Utilities 005 * $Id: FacadeProxyInvocationHandler.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/FacadeProxyInvocationHandler.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.Array; 024import java.lang.reflect.InvocationHandler; 025import java.lang.reflect.Method; 026import java.lang.reflect.Proxy; 027import java.util.IdentityHashMap; 028import java.util.stream.Collectors; 029import lombok.NoArgsConstructor; 030import lombok.ToString; 031 032import static lombok.AccessLevel.PROTECTED; 033 034/** 035 * {@link InvocationHandler} abstract base class to "extend" concrete 036 * implementation {@link Class}es by adding "facade" interfaces. See 037 * {@link #getProxyClassFor(Object)}. 038 * 039 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball} 040 * @version $Revision: 5431 $ 041 */ 042@NoArgsConstructor(access = PROTECTED) @ToString 043public abstract class FacadeProxyInvocationHandler 044 extends DefaultInvocationHandler { 045 private final ProxyMap map = new ProxyMap(); 046 047 /** 048 * Method to return an extended {@link Proxy} implementing "facade" 049 * interfaces for {@code in} if {@link #getProxyClassFor(Object)} 050 * returns non-{@code null}. 051 * 052 * @param in The {@link Object} to extend. 053 * 054 * @return A {@link Proxy} if {@link #getProxyClassFor(Object)} returns 055 * non-{@code null}; {@code in} otherwise. 056 */ 057 public Object enhance(Object in) { 058 Object out = null; 059 060 if (! hasFacade(in)) { 061 Class<?> type = getProxyClassFor(in); 062 063 if (type != null) { 064 out = map.computeIfAbsent(in, k -> compute(type)); 065 } 066 } 067 068 return (out != null) ? out : in; 069 } 070 071 private <T> T compute(Class<T> type) { 072 T proxy = null; 073 074 try { 075 proxy = 076 type 077 .getConstructor(InvocationHandler.class) 078 .newInstance(this); 079 } catch (RuntimeException exception) { 080 throw exception; 081 } catch (Exception exception) { 082 throw new IllegalStateException(exception); 083 } 084 085 return proxy; 086 } 087 088 /** 089 * Method provided by subclasses to provide the {@link Proxy} 090 * {@link Class} if the input {@link Object} should be extended. 091 * 092 * @param object The {@link Object} that may or may not be 093 * extended. 094 * 095 * @return A {@link Proxy} {@link Class} if the {@link Object} should 096 * be extended; {@code null} otherwise. 097 */ 098 protected abstract Class<?> getProxyClassFor(Object object); 099 100 private boolean hasFacade(Object object) { 101 return reverseOf(object) != null; 102 } 103 104 private Object reverseOf(Object in) { 105 Object out = null; 106 107 if (in instanceof Proxy && Proxy.isProxyClass(in.getClass())) { 108 InvocationHandler handler = Proxy.getInvocationHandler(in); 109 110 if (handler instanceof FacadeProxyInvocationHandler) { 111 out = 112 ((FacadeProxyInvocationHandler) handler) 113 .map.reverse().get(in); 114 } 115 } 116 117 return out; 118 } 119 120 private Object reverseFor(Class<?> type, Object in) { 121 Object out = null; 122 123 if (out == null) { 124 Object object = reverseOf(in); 125 126 if (object != null && type.isAssignableFrom(object.getClass())) { 127 out = object; 128 } 129 } 130 131 if (out == null) { 132 if (in instanceof Object[]) { 133 if (type.isArray()) { 134 int length = ((Object[]) in).length; 135 136 out = Array.newInstance(type.getComponentType(), length); 137 138 for (int i = 0; i < length; i += 1) { 139 ((Object[]) out)[i] = 140 reverseFor(type.getComponentType(), 141 ((Object[]) in)[i]); 142 } 143 } 144 } 145 } 146 147 return (out != null) ? out : in; 148 } 149 150 private Object[] reverseFor(Class<?>[] types, Object[] in) { 151 Object[] out = null; 152 153 if (in != null) { 154 out = new Object[in.length]; 155 156 for (int i = 0; i < out.length; i += 1) { 157 out[i] = reverseFor(types[i], in[i]); 158 } 159 } 160 161 return (out != null) ? out : in; 162 } 163 164 @Override 165 public Object invoke(Object proxy, 166 Method method, Object[] argv) throws Throwable { 167 Object result = null; 168 Class<?> declarer = method.getDeclaringClass(); 169 Object that = map.reverse.get(proxy); 170 171 if (declarer.isAssignableFrom(Object.class)) { 172 result = method.invoke(that, argv); 173 } else { 174 argv = reverseFor(method.getParameterTypes(), argv); 175 176 if (declarer.isAssignableFrom(that.getClass())) { 177 result = method.invoke(that, argv); 178 } else { 179 result = super.invoke(proxy, method, argv); 180 } 181 } 182 183 return enhance(result); 184 } 185 186 @NoArgsConstructor 187 private class ProxyMap extends IdentityHashMap<Object,Object> { 188 private static final long serialVersionUID = 6708505296087349421L; 189 190 private final IdentityHashMap<Object,Object> reverse = 191 new IdentityHashMap<>(); 192 193 public IdentityHashMap<Object,Object> reverse() { return reverse; } 194 195 @Override 196 public Object put(Object key, Object value) { 197 reverse().put(value, key); 198 199 return super.put(key, value); 200 } 201 } 202}