001package ball.lang.reflect;
002/*-
003 * ##########################################################################
004 * Utilities
005 * $Id: DefaultInvocationHandler.java 7215 2021-01-03 18:39:51Z ball $
006 * $HeadURL: svn+ssh://svn.hcf.dev/var/spool/scm/repository.svn/ball-util/trunk/src/main/java/ball/lang/reflect/DefaultInvocationHandler.java $
007 * %%
008 * Copyright (C) 2008 - 2021 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: 7215 $
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     * If the {@link Method#isDefault() method.isDefault()}, that
096     * {@link Method} will be invoked directly.  If the {@link Method} is
097     * declared in {@link Object}, it is applied to {@link.this}
098     * {@link InvocationHandler}.  Otherwise, the call will be dispatched to
099     * a declared {@link Method} on {@link.this} {@link InvocationHandler}
100     * with the same name and compatible parameter types (forcing access if
101     * necessary).
102     *
103     * @param   proxy           The proxy instance.
104     * @param   method          The {@link Method}.
105     * @param   argv            The argument array.
106     *
107     * @return  The value to return from the {@link Method} invocation.
108     *
109     * @throws  Exception       If no compatible {@link Method} is found or
110     *                          the {@link Method} cannot be invoked.
111     */
112    @Override
113    public Object invoke(Object proxy, Method method, Object[] argv) throws Throwable {
114        Object result = null;
115        Class<?> declarer = method.getDeclaringClass();
116
117        if (method.isDefault()) {
118            result =
119                DefaultInterfaceMethodInvocationHandler.super
120                .invoke(proxy, method, argv);
121        } else if (declarer.equals(Object.class)) {
122            result = method.invoke(this, argv);
123        } else {
124            result =
125                invokeMethod(this, true,
126                             method.getName(),
127                             argv, method.getParameterTypes());
128        }
129
130        return result;
131    }
132
133    /**
134     * Method available to subclass implementations to get the implemented
135     * interfaces of the argument {@link Class types}.  The default
136     * implementation caches the results.
137     *
138     * @param   type            The {@link Class} to analyze.
139     * @param   types           Additional {@link Class}es to analyze.
140     *
141     * @return  The {@link List} of interface {@link Class}es.
142     */
143    protected List<Class<?>> getImplementedInterfacesOf(Class<?> type, Class<?>... types) {
144        Set<Class<?>> set =
145            Stream.concat(Stream.of(type), Arrays.stream(types))
146            .filter(Objects::nonNull)
147            .flatMap(t -> cache.computeIfAbsent(t, k -> compute(k)).stream())
148            .collect(Collectors.toCollection(LinkedHashSet::new));
149
150        return new ArrayList<>(set);
151    }
152
153    private List<Class<?>> compute(Class<?> type) {
154        Set<Class<?>> set =
155            Stream.concat(Stream.of(type), getAllInterfaces(type).stream())
156            .filter(Class::isInterface)
157            .collect(Collectors.toCollection(LinkedHashSet::new));
158
159        return new ArrayList<>(set);
160    }
161}