001package ball.lang.reflect;
002/*-
003 * ##########################################################################
004 * Utilities
005 * $Id: DefaultInvocationHandler.html 6489 2020-07-19 17:22:55Z ball $
006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/hcf-dev/blog/DRAFT-java-multi-release-jars/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.reflect.InvocationHandler;
024import java.lang.reflect.Method;
025import java.lang.reflect.Proxy;
026import java.util.ArrayList;
027import java.util.Arrays;
028import java.util.HashMap;
029import java.util.LinkedHashSet;
030import java.util.List;
031import java.util.Objects;
032import java.util.Set;
033import java.util.stream.Collectors;
034import java.util.stream.Stream;
035import lombok.NoArgsConstructor;
036import lombok.ToString;
037
038import static org.apache.commons.lang3.ClassUtils.getAllInterfaces;
039import static org.apache.commons.lang3.reflect.MethodUtils.invokeMethod;
040
041/**
042 * Default {@link InvocationHandler} implementation.
043 * See {@link #invoke(Object,Method,Object[])}.
044 *
045 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
046 * @version $Revision: 6489 $
047 */
048@NoArgsConstructor @ToString
049public class DefaultInvocationHandler implements DefaultInterfaceMethodInvocationHandler {
050    private final HashMap<Class<?>,List<Class<?>>> cache = new HashMap<>();
051
052    /**
053     * See {@link Proxy#getProxyClass(ClassLoader,Class[])}.
054     *
055     * @param   interfaces      The interface {@link Class}es the
056     *                          {@link Proxy} {@link Class} will implement.
057     *
058     * @return  The {@link Proxy}.
059     */
060    @SuppressWarnings({ "deprecation" })
061    public Class<?> getProxyClass(Class<?>... interfaces) throws IllegalArgumentException {
062        return Proxy.getProxyClass(getClass().getClassLoader(), interfaces);
063    }
064
065    /**
066     * See
067     * {@link Proxy#newProxyInstance(ClassLoader,Class[],InvocationHandler)}.
068     * Default implementation invokes {@link #getProxyClass(Class...)}.
069     *
070     * @param   interfaces      The interface {@link Class}es the
071     *                          {@link Proxy} {@link Class} will implement.
072     *
073     * @return  The {@link Proxy}.
074     */
075    public Object newProxyInstance(Class<?>... interfaces) throws IllegalArgumentException {
076        Object proxy = null;
077
078        try {
079            proxy =
080                getProxyClass(interfaces)
081                .getConstructor(InvocationHandler.class)
082                .newInstance(this);
083        } catch (IllegalArgumentException exception) {
084            throw exception;
085        } catch (RuntimeException exception) {
086            throw exception;
087        } catch (Exception exception) {
088            throw new IllegalStateException(exception);
089        }
090
091        return proxy;
092    }
093
094    /**
095     * {@inheritDoc}
096     *
097     * If the {@link Method#isDefault() method.isDefault()}, that
098     * {@link Method} will be invoked directly.  If the {@link Method} is
099     * declared in {@link Object}, it is applied to {@link.this}
100     * {@link InvocationHandler}.  Otherwise, the call will be dispatched to
101     * a declared {@link Method} on {@link.this} {@link InvocationHandler}
102     * with the same name and compatible parameter types (forcing access if
103     * necessary).
104     *
105     * @throws  Exception       If no compatible {@link Method} is found or
106     *                          the {@link Method} cannot be invoked.
107     */
108    @Override
109    public Object invoke(Object proxy,
110                         Method method, Object[] argv) throws Throwable {
111        Object result = null;
112        Class<?> declarer = method.getDeclaringClass();
113
114        if (method.isDefault()) {
115            result =
116                DefaultInterfaceMethodInvocationHandler.super
117                .invoke(proxy, method, argv);
118        } else if (declarer.equals(Object.class)) {
119            result = method.invoke(this, argv);
120        } else {
121            result =
122                invokeMethod(this, true,
123                             method.getName(),
124                             argv, method.getParameterTypes());
125        }
126
127        return result;
128    }
129
130    /**
131     * Method available to subclass implementations to get the implemented
132     * interfaces of the argument {@link Class types}.  The default
133     * implementation caches the results.
134     *
135     * @param   type            The {@link Class} to analyze.
136     * @param   types           Additional {@link Class}es to analyze.
137     *
138     * @return  The {@link List} of interface {@link Class}es.
139     */
140    protected List<Class<?>> getImplementedInterfacesOf(Class<?> type,
141                                                        Class<?>... types) {
142        Set<Class<?>> set =
143            Stream.concat(Stream.of(type), Arrays.stream(types))
144            .filter(Objects::nonNull)
145            .flatMap(t -> cache.computeIfAbsent(t, k -> compute(k)).stream())
146            .collect(Collectors.toCollection(LinkedHashSet::new));
147
148        return new ArrayList<>(set);
149    }
150
151    private List<Class<?>> compute(Class<?> type) {
152        Set<Class<?>> set =
153            Stream.concat(Stream.of(type), getAllInterfaces(type).stream())
154            .filter(Class::isInterface)
155            .collect(Collectors.toCollection(LinkedHashSet::new));
156
157        return new ArrayList<>(set);
158    }
159}