001package ball.lang.reflect;
002/*-
003 * ##########################################################################
004 * Utilities
005 * %%
006 * Copyright (C) 2008 - 2022 Allen D. Ball
007 * %%
008 * Licensed under the Apache License, Version 2.0 (the "License");
009 * you may not use this file except in compliance with the License.
010 * You may obtain a copy of the License at
011 *
012 *      http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing, software
015 * distributed under the License is distributed on an "AS IS" BASIS,
016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017 * See the License for the specific language governing permissions and
018 * limitations under the License.
019 * ##########################################################################
020 */
021import java.lang.reflect.InvocationHandler;
022import java.lang.reflect.Method;
023import java.lang.reflect.Proxy;
024import java.util.ArrayList;
025import java.util.Arrays;
026import java.util.HashMap;
027import java.util.LinkedHashSet;
028import java.util.List;
029import java.util.Objects;
030import java.util.Set;
031import java.util.stream.Collectors;
032import java.util.stream.Stream;
033import lombok.NoArgsConstructor;
034import lombok.ToString;
035
036import static org.apache.commons.lang3.ClassUtils.getAllInterfaces;
037import static org.apache.commons.lang3.reflect.MethodUtils.invokeMethod;
038
039/**
040 * Default {@link InvocationHandler} implementation.
041 * See {@link #invoke(Object,Method,Object[])}.
042 *
043 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball}
044 */
045@NoArgsConstructor @ToString
046public class DefaultInvocationHandler implements DefaultInterfaceMethodInvocationHandler {
047    private final HashMap<Class<?>,List<Class<?>>> cache = new HashMap<>();
048
049    /**
050     * See {@link Proxy#getProxyClass(ClassLoader,Class[])}.
051     *
052     * @param   interfaces      The interface {@link Class}es the
053     *                          {@link Proxy} {@link Class} will implement.
054     *
055     * @return  The {@link Proxy}.
056     */
057    @SuppressWarnings({ "deprecation" })
058    public Class<?> getProxyClass(Class<?>... interfaces) throws IllegalArgumentException {
059        return Proxy.getProxyClass(getClass().getClassLoader(), interfaces);
060    }
061
062    /**
063     * See
064     * {@link Proxy#newProxyInstance(ClassLoader,Class[],InvocationHandler)}.
065     * Default implementation invokes {@link #getProxyClass(Class...)}.
066     *
067     * @param   interfaces      The interface {@link Class}es the
068     *                          {@link Proxy} {@link Class} will implement.
069     *
070     * @return  The {@link Proxy}.
071     */
072    public Object newProxyInstance(Class<?>... interfaces) throws IllegalArgumentException {
073        Object proxy = null;
074
075        try {
076            proxy =
077                getProxyClass(interfaces)
078                .getConstructor(InvocationHandler.class)
079                .newInstance(this);
080        } catch (IllegalArgumentException exception) {
081            throw exception;
082        } catch (RuntimeException exception) {
083            throw exception;
084        } catch (Exception exception) {
085            throw new IllegalStateException(exception);
086        }
087
088        return proxy;
089    }
090
091    /**
092     * If the {@link Method#isDefault() method.isDefault()}, that
093     * {@link Method} will be invoked directly.  If the {@link Method} is
094     * declared in {@link Object}, it is applied to {@link.this}
095     * {@link InvocationHandler}.  Otherwise, the call will be dispatched to
096     * a declared {@link Method} on {@link.this} {@link InvocationHandler}
097     * with the same name and compatible parameter types (forcing access if
098     * necessary).
099     *
100     * @param   proxy           The proxy instance.
101     * @param   method          The {@link Method}.
102     * @param   argv            The argument array.
103     *
104     * @return  The value to return from the {@link Method} invocation.
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, Method method, Object[] argv) throws Throwable {
111        Object result = null;
112        Class<?> declarer = method.getDeclaringClass();
113
114        if (method.isDefault()) {
115            result = DefaultInterfaceMethodInvocationHandler.super.invoke(proxy, method, argv);
116        } else if (declarer.equals(Object.class)) {
117            result = method.invoke(this, argv);
118        } else {
119            result = invokeMethod(this, true, method.getName(), argv, method.getParameterTypes());
120        }
121
122        return result;
123    }
124
125    /**
126     * Method available to subclass implementations to get the implemented
127     * interfaces of the argument {@link Class types}.  The default
128     * implementation caches the results.
129     *
130     * @param   type            The {@link Class} to analyze.
131     * @param   types           Additional {@link Class}es to analyze.
132     *
133     * @return  The {@link List} of interface {@link Class}es.
134     */
135    protected List<Class<?>> getImplementedInterfacesOf(Class<?> type, Class<?>... types) {
136        Set<Class<?>> set =
137            Stream.concat(Stream.of(type), Arrays.stream(types))
138            .filter(Objects::nonNull)
139            .flatMap(t -> cache.computeIfAbsent(t, k -> compute(k)).stream())
140            .collect(Collectors.toCollection(LinkedHashSet::new));
141
142        return new ArrayList<>(set);
143    }
144
145    private List<Class<?>> compute(Class<?> type) {
146        Set<Class<?>> set =
147            Stream.concat(Stream.of(type), getAllInterfaces(type).stream())
148            .filter(Class::isInterface)
149            .collect(Collectors.toCollection(LinkedHashSet::new));
150
151        return new ArrayList<>(set);
152    }
153}