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