All Downloads are FREE. Search and download functionalities are using the official Maven repository.

name.remal.proxy.CompositeInvocationHandler Maven / Gradle / Ivy

package name.remal.proxy;

import name.remal.proxy.MethodHandler.CanHandle;
import name.remal.proxy.MethodHandler.Handler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import static java.lang.Integer.toHexString;
import static java.lang.System.identityHashCode;
import static java.util.Objects.requireNonNull;

public class CompositeInvocationHandler implements InvocationHandler {

    private static final boolean areDefaultMethodsSupported = areDefaultMethodsSupported();

    private static boolean areDefaultMethodsSupported() {
        try {
            Method.class.getDeclaredMethod("isDefault");
            return true;
        } catch (NoSuchMethodException e) {
            return false;
        }
    }

    private final List<@NotNull MethodHandler> methodHandlers = new ArrayList<>();

    @Override
    @Nullable
    public Object invoke(@NotNull Object proxy, @NotNull Method method, @Nullable Object[] args) throws Throwable {
        for (MethodHandler methodHandler : methodHandlers) {
            if (methodHandler.canHandle(method)) {
                return methodHandler.handle(proxy, method, args);
            }
        }

        if (areDefaultMethodsSupported) {
            if (method.isDefault()) {
                MethodHandle methodHandle = MethodHandles.lookup()
                    .in(method.getDeclaringClass())
                    .unreflectSpecial(method, method.getDeclaringClass())
                    .bindTo(proxy);
                if (args == null) {
                    return methodHandle.invoke();
                } else {
                    return methodHandle.invoke(args);
                }
            }
        }

        if (EQUALS_CAN_HANDLE.canHandle(method)) {
            return OBJECT_EQUALS_HANDLER.handle(proxy, requireNonNull(args)[0]);
        } else if (HASH_CODE_CAN_HANDLE.canHandle(method)) {
            return OBJECT_HASH_CODE_HANDLER.handle(proxy);
        } else if (TO_STRING_CAN_HANDLE.canHandle(method)) {
            return OBJECT_TO_STRING_HANDLER.handle(proxy);
        }

        throw new UnsupportedOperationException(method.toString());
    }


    @NotNull
    public CompositeInvocationHandler prependMethodHandler(@NotNull MethodHandler methodHandler) {
        methodHandlers.add(0, methodHandler);
        return this;
    }

    @NotNull
    public CompositeInvocationHandler prependMethodHandler(@NotNull CanHandle canHandle, @NotNull Handler handler) {
        return prependMethodHandler(MethodHandler.of(canHandle, handler));
    }

    @NotNull
    public CompositeInvocationHandler prependConstMethodHandler(@NotNull CanHandle canHandle, @Nullable Object predefinedReturnValue) {
        return prependMethodHandler(canHandle, (proxy, method, args) -> predefinedReturnValue);
    }

    @NotNull
    public CompositeInvocationHandler appendMethodHandler(@NotNull MethodHandler methodHandler) {
        methodHandlers.add(methodHandler);
        return this;
    }

    @NotNull
    public CompositeInvocationHandler appendMethodHandler(@NotNull CanHandle canHandle, @NotNull Handler handler) {
        return appendMethodHandler(MethodHandler.of(canHandle, handler));
    }

    @NotNull
    public CompositeInvocationHandler appendConstMethodHandler(@NotNull CanHandle canHandle, @Nullable Object predefinedReturnValue) {
        return appendMethodHandler(canHandle, (proxy, method, args) -> predefinedReturnValue);
    }


    @NotNull
    public static final CanHandle EQUALS_CAN_HANDLE = method -> method.getParameterCount() == 1 && "equals".equals(method.getName());

    @FunctionalInterface
    public interface EqualsHandler {
        boolean handle(@NotNull Object proxy, @Nullable Object other) throws Throwable;
    }

    @NotNull
    public static final EqualsHandler OBJECT_EQUALS_HANDLER = (proxy, other) -> proxy == other;

    @NotNull
    public CompositeInvocationHandler prependObjectEqualsHandler() {
        return prependMethodHandler(EQUALS_CAN_HANDLE, (proxy, method, args) -> OBJECT_EQUALS_HANDLER.handle(proxy, requireNonNull(args)[0]));
    }

    @NotNull
    public CompositeInvocationHandler appendObjectEqualsHandler() {
        return appendMethodHandler(EQUALS_CAN_HANDLE, (proxy, method, args) -> OBJECT_EQUALS_HANDLER.handle(proxy, requireNonNull(args)[0]));
    }

    @NotNull
    private static Handler wrap(@NotNull EqualsHandler handler) {
        return (proxy, method, args) -> {
            Object other = requireNonNull(args)[0];
            if (other == null) return Boolean.FALSE;
            if (proxy == other) return Boolean.TRUE;
            return handler.handle(proxy, other);
        };
    }

    @NotNull
    public CompositeInvocationHandler prependEqualsHandler(@NotNull EqualsHandler handler) {
        return prependMethodHandler(EQUALS_CAN_HANDLE, wrap(handler));
    }

    @NotNull
    @SuppressWarnings("ConstantConditions")
    public CompositeInvocationHandler appendEqualsHandler(@NotNull EqualsHandler handler) {
        return appendMethodHandler(EQUALS_CAN_HANDLE, wrap(handler));
    }


    @NotNull
    public static final CanHandle HASH_CODE_CAN_HANDLE = method -> method.getParameterCount() == 0 && "hashCode".equals(method.getName());

    @FunctionalInterface
    public interface HashCodeHandler {
        int handle(@NotNull Object proxy) throws Throwable;
    }

    @NotNull
    public static final HashCodeHandler OBJECT_HASH_CODE_HANDLER = System::identityHashCode;

    @NotNull
    public CompositeInvocationHandler prependObjectHashCodeHandler() {
        return prependHashCodeHandler(OBJECT_HASH_CODE_HANDLER);
    }

    @NotNull
    public CompositeInvocationHandler appendObjectHashCodeHandler() {
        return appendHashCodeHandler(OBJECT_HASH_CODE_HANDLER);
    }

    @NotNull
    public CompositeInvocationHandler prependHashCodeHandler(@NotNull HashCodeHandler handler) {
        return prependMethodHandler(HASH_CODE_CAN_HANDLE, (proxy, method, args) -> handler.handle(proxy));
    }

    @NotNull
    public CompositeInvocationHandler prependHashCodeHandler(int predefinedReturnValue) {
        return prependHashCodeHandler(__ -> predefinedReturnValue);
    }

    @NotNull
    public CompositeInvocationHandler appendHashCodeHandler(@NotNull HashCodeHandler handler) {
        return appendMethodHandler(HASH_CODE_CAN_HANDLE, (proxy, method, args) -> handler.handle(proxy));
    }

    @NotNull
    public CompositeInvocationHandler appendHashCodeHandler(int predefinedReturnValue) {
        return appendHashCodeHandler(__ -> predefinedReturnValue);
    }


    @NotNull
    public static final CanHandle TO_STRING_CAN_HANDLE = method -> method.getParameterCount() == 0 && "toString".equals(method.getName());

    @FunctionalInterface
    public interface ToStringHandler {
        @NotNull
        String handle(@NotNull Object proxy) throws Throwable;
    }

    @NotNull
    public static final ToStringHandler OBJECT_TO_STRING_HANDLER = proxy -> proxy.getClass().getName() + '@' + toHexString(identityHashCode(proxy));

    @NotNull
    public CompositeInvocationHandler prependObjectToStringHandler() {
        return prependToStringHandler(OBJECT_TO_STRING_HANDLER);
    }

    @NotNull
    public CompositeInvocationHandler appendObjectToStringHandler() {
        return appendToStringHandler(OBJECT_TO_STRING_HANDLER);
    }

    @NotNull
    public CompositeInvocationHandler prependToStringHandler(@NotNull ToStringHandler handler) {
        return prependMethodHandler(TO_STRING_CAN_HANDLE, (proxy, method, args) -> handler.handle(proxy));
    }

    @NotNull
    public CompositeInvocationHandler prependToStringHandler(@NotNull String predefinedReturnValue) {
        return prependToStringHandler(__ -> predefinedReturnValue);
    }

    @NotNull
    public CompositeInvocationHandler appendToStringHandler(@NotNull ToStringHandler handler) {
        return appendMethodHandler(TO_STRING_CAN_HANDLE, (proxy, method, args) -> handler.handle(proxy));
    }

    @NotNull
    public CompositeInvocationHandler appendToStringHandler(@NotNull String predefinedReturnValue) {
        return appendToStringHandler(__ -> predefinedReturnValue);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy