ProxyKit.java

package space.sunqian.fs.dynamic.proxy;

import space.sunqian.annotation.Nonnull;
import space.sunqian.annotation.Nullable;
import space.sunqian.annotation.OutParam;
import space.sunqian.fs.base.system.JvmKit;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Proxy utilities.
 *
 * @author sunqian
 */
public class ProxyKit {

    /**
     * Returns a map that contains all proxiable methods of the given class and interfaces. Its keys are the specified
     * proxied types, and the values are the proxiable methods under that type. A proxiable method is public or
     * protected, and non-static and non-final. If there are methods with the same name and same JVM descriptor, only
     * the first one encountered is added into the map.
     * <p>
     * This method is a helpful method for the {@link ProxyMaker} implementations.
     *
     * @param proxiedClass the class to be proxied, may be {@code null} if it is {@link Object}
     * @param interfaces   the interfaces to be proxied
     * @param proxyHandler the proxy handler
     * @return a map that contains all proxiable methods of the given class and interfaces
     */
    public static @Nonnull Map<Class<?>, List<Method>> getProxiableMethods(
        @Nullable Class<?> proxiedClass,
        @Nonnull List<@Nonnull Class<?>> interfaces,
        @Nonnull ProxyHandler proxyHandler
    ) {
        Map<Class<?>, List<Method>> result = new LinkedHashMap<>();
        Set<String> ids = new HashSet<>();
        Class<?> superclass = proxiedClass == null ? Object.class : proxiedClass;
        for (Method method : superclass.getMethods()) {
            putProxiableMethod(method, proxyHandler, ids, superclass, result);
        }
        for (Method method : superclass.getDeclaredMethods()) {
            putProxiableMethod(method, proxyHandler, ids, superclass, result);
        }
        for (Class<?> anInterface : interfaces) {
            for (Method method : anInterface.getMethods()) {
                putProxiableMethod(method, proxyHandler, ids, anInterface, result);
            }
        }
        return result;
    }

    private static boolean isProxiable(@Nonnull Method method, @Nonnull ProxyHandler handler) {
        if (!isProxiable(method)) {
            return false;
        }
        return handler.needsProxy(method);
    }

    /**
     * Returns whether the given method is proxiable. A proxiable method cannot be static, final, or synthetic, and
     * should be public or protected.
     *
     * @param method the method to be checked
     * @return whether the given method is proxiable
     */
    public static boolean isProxiable(@Nonnull Method method) {
        if (method.isSynthetic()) {
            return false;
        }
        int mod = method.getModifiers();
        if (Modifier.isStatic(mod) || Modifier.isFinal(mod)) {
            return false;
        }
        return Modifier.isPublic(mod) || Modifier.isProtected(mod);
    }

    private static void putProxiableMethod(
        @Nonnull Method method,
        @Nonnull ProxyHandler handler,
        @Nonnull Set<String> ids,
        @Nonnull Class<?> type,
        @Nonnull @OutParam Map<Class<?>, List<Method>> result
    ) {
        if (isProxiable(method, handler)) {
            String methodId = buildMethodId(method);
            if (!ids.contains(methodId)) {
                ids.add(methodId);
                result.compute(type, (k, v) -> {
                    if (v == null) {
                        v = new ArrayList<>();
                    }
                    v.add(method);
                    return v;
                });
            }
        }
    }

    private static @Nonnull String buildMethodId(@Nonnull Method method) {
        return method.getName() + ":" + JvmKit.toDescriptor(method);
    }

    private ProxyKit() {
    }
}