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}