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