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

dev.vality.woody.api.proxy.ProxyInvocationHandler Maven / Gradle / Ivy

There is a newer version: 2.0.8
Show newest version
package dev.vality.woody.api.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.BiFunction;

public final class ProxyInvocationHandler implements InvocationHandler {

    private final Map callMap;
    private final InvocationTargetProvider targetProvider;

    public ProxyInvocationHandler(Class iface, InvocationTargetProvider targetProvider,
                                  MethodCallerFactory callerFactory, MethodCallInterceptor callInterceptor) {
        this.targetProvider = targetProvider;
        this.callMap = createCallMap(callInterceptor, targetProvider, iface, callerFactory);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        CallerBundle methodCallerBundle = callMap.get(method);
        if (methodCallerBundle != null) {
            return methodCallerBundle.interceptor.intercept(proxy, args, methodCallerBundle.caller);
        } else {
            return method.invoke(targetProvider.getTarget(), args);
        }
    }

    private Map createCallMap(MethodCallInterceptor callInterceptor,
                                                    InvocationTargetProvider targetProvider, Class iface,
                                                    MethodCallerFactory callerFactory) {
        Class targetType = targetProvider.getTargetType();

        if (!iface.isAssignableFrom(targetType)) {
            throw new IllegalArgumentException("Target object class doesn't implement referred interface");
        }
        Map callerMap = new TreeMap<>(MethodShadow.METHOD_COMPARATOR);
        Method[] targetIfaceMethods = MethodShadow.getShadowedMethods(targetType, iface);

        for (Method method : targetIfaceMethods) {
            callerMap.put(MethodShadow.getSameMethod(method, iface),
                    new CallerBundle(callerFactory.getInstance(targetProvider, method), callInterceptor));
        }

        return addObjectMethods(iface, callerFactory, callerMap);
    }

    private Map addObjectMethods(Class iface, MethodCallerFactory callerFactory,
                                                       Map callerMap) {
        SingleTargetProvider objTargetProvider =
                new SingleTargetProvider(Object.class, this);//ref leak on init, assume it's a trusted code
        MethodCallInterceptor directCallInterceptor = MethodCallInterceptors.directCallInterceptor();
        BiFunction targetExtractor = (src, stub) -> {
            InvocationHandler invocationHandler = null;
            try {
                invocationHandler = Proxy.getInvocationHandler(src);
                if (!(invocationHandler instanceof ProxyInvocationHandler)) {
                    invocationHandler = null;
                }
            } catch (IllegalArgumentException ignored) {
                //ignore
            }
            return invocationHandler == null ? stub : invocationHandler;
        };

        try {
            //it's expected that this handler is bound only for one dedicated proxy
            Method objMethod = objTargetProvider.getClass().getMethod("hashCode");
            callerMap.put(objMethod, new CallerBundle(callerFactory.getInstance(objTargetProvider, objMethod,
                    (src, args) -> targetExtractor.apply(src, ProxyInvocationHandler.this).hashCode()),
                    directCallInterceptor));
            objMethod = objTargetProvider.getClass().getMethod("toString");
            callerMap.put(objMethod, new CallerBundle(callerFactory.getInstance(objTargetProvider, objMethod,
                    (src, args) -> iface.getName() + "@" +
                            targetExtractor.apply(src, ProxyInvocationHandler.this).hashCode()),
                    directCallInterceptor));

            objMethod = objTargetProvider.getClass().getMethod("equals", Object.class);
            callerMap.put(objMethod, new CallerBundle(callerFactory.getInstance(objTargetProvider, objMethod,
                    (src, args) -> targetExtractor.apply(args[0], null) == ProxyInvocationHandler.this),
                    directCallInterceptor));
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException("Object methods're not found", e);
        }
        return callerMap;
    }

    private static class CallerBundle {
        private final InstanceMethodCaller caller;
        private final MethodCallInterceptor interceptor;

        public CallerBundle(InstanceMethodCaller caller, MethodCallInterceptor interceptor) {
            this.caller = caller;
            this.interceptor = interceptor;
        }

    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy