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}