Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
net.e6tech.elements.common.interceptor.Interceptor Maven / Gradle / Ivy
/*
* Copyright 2016 Futeh Kao
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.e6tech.elements.common.interceptor;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.description.modifier.Ownership;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.loading.ClassInjector;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.MethodCall;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.SuperMethodCall;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.This;
import net.bytebuddy.matcher.ElementMatchers;
import net.e6tech.elements.common.reflection.Primitives;
import net.e6tech.elements.common.reflection.Reflection;
import net.e6tech.elements.common.resources.Provision;
import net.e6tech.elements.common.util.SystemException;
import net.e6tech.elements.common.util.concurrent.ObjectPool;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/**
* Created by futeh.
*/
@SuppressWarnings("unchecked")
public class Interceptor {
static final String HANDLER_FIELD = "handler";
private static Interceptor instance = new Interceptor();
private static ThreadLocal anonymousThreadLocal = new ThreadLocal<>();
private int initialCapacity = 100;
private int maximumSize = 2000;
private long expiration = 180 * 60 * 1000L; // three hours
private Cache proxyClasses;
Cache singletonClasses;
private Cache anonymousClasses;
public static Interceptor getInstance() {
return instance;
}
public Interceptor() {
initialize();
}
public int getInitialCapacity() {
return initialCapacity;
}
public void setInitialCapacity(int initialCapacity) {
this.initialCapacity = initialCapacity;
}
public int getMaximumSize() {
return maximumSize;
}
public void setMaximumSize(int maximumSize) {
this.maximumSize = maximumSize;
}
public long getExpiration() {
return expiration;
}
public void setExpiration(long expiration) {
this.expiration = expiration;
}
private Cache createCache() {
return CacheBuilder.newBuilder()
.initialCapacity(initialCapacity)
.maximumSize(maximumSize)
.expireAfterWrite(expiration, TimeUnit.MILLISECONDS)
.concurrencyLevel(Provision.cacheBuilderConcurrencyLevel)
.build();
}
public void initialize() {
proxyClasses = createCache();
singletonClasses = createCache();
anonymousClasses = createCache();
}
Class extends T> loadClass(DynamicType.Unloaded unloaded, Class cls, ClassLoader classLoader) {
DynamicType.Loaded loaded;
try {
if (classLoader != null) {
loaded = unloaded.load(classLoader);
} else if (cls.getClassLoader() == null) {
loaded = unloaded.load(getClass().getClassLoader());
} else {
loaded = unloaded.load(cls.getClassLoader());
}
} catch (NoClassDefFoundError ex) {
ClassLoader delegateLoader = cls.getClassLoader();
if (delegateLoader == null) {
delegateLoader = ClassLoader.getSystemClassLoader();
}
loaded = unloaded.load(new JoinClassLoader(getClass().getClassLoader(), delegateLoader));
}
return loaded.getLoaded();
}
// NEVER cache prototype class because the cls could be the same while prototype changes.
public Class newPrototypeClass(Class cls, T prototype) {
return newPrototypeClass(cls, prototype, null);
}
/**
* Creates a prototype class. When an instance is created, its bean properties are copied from the prototype.
* Afterward, the instance functions independently from the prototype.
* @param cls prototype class
* @param prototype prototype instance
* @param type of prototype
* @return byte manipulated prototype class
*/
@SuppressWarnings("unchecked")
public Class newPrototypeClass(Class cls, T prototype, ClassLoader classLoader) {
// NEVER cache prototype class because the cls could be the same while prototype changes.
DynamicType.Unloaded unloaded = new ByteBuddy()
.subclass(cls)
.constructor(ElementMatchers.any())
.intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.to(new PrototypeConstructor<>(prototype))))
.make();
return (Class) loadClass(unloaded, cls, classLoader);
}
// must be public static
public static class PrototypeConstructor {
T prototype;
public PrototypeConstructor(T prototype) {
this.prototype = prototype;
}
public void construct(@This Object instance) {
if (prototype != null)
Reflection.copyInstance(instance, prototype);
}
}
public SingletonClassBuilder singletonClassBuilder(Class cls, T singleton) {
return new SingletonClassBuilder<>(this, cls, singleton);
}
/*
* Create a class that returns a singleton. When new instance of the class is created, all operations, except
* for finalize, are delegated to the singleton.
*/
public Class newSingletonClass(Class cls, T singleton) {
return singletonClassBuilder(cls, singleton).build();
}
DynamicType.Builder newSingletonBuilder(Class cls) {
return new ByteBuddy()
.subclass(cls)
.method(ElementMatchers.any().and(ElementMatchers.not(ElementMatchers.named("finalize").and(ElementMatchers.hasParameters(ElementMatchers.none())))))
.intercept(MethodDelegation.toField(HANDLER_FIELD))
.defineField(HANDLER_FIELD, Handler.class, Visibility.PRIVATE, Ownership.STATIC);
}
private ClassLoadingStrategy getClassLoadingStrategy(MethodHandles.Lookup lookup, Class targetClass) {
ClassLoadingStrategy strategy;
try {
if (ClassInjector.UsingLookup.isAvailable()) {
Class> methodHandles = Class.forName("java.lang.invoke.MethodHandles");
Method lookupMethod = methodHandles.getMethod("lookup");
if (lookup == null) {
lookup = (MethodHandles.Lookup) lookupMethod.invoke(null);
}
Method privateLookupIn = methodHandles.getMethod("privateLookupIn",
Class.class,
Class.forName("java.lang.invoke.MethodHandles$Lookup"));
Object privateLookup = privateLookupIn.invoke(null, targetClass, lookup);
strategy = ClassLoadingStrategy.UsingLookup.of(privateLookup);
} else if (ClassInjector.UsingReflection.isAvailable()) {
strategy = ClassLoadingStrategy.Default.INJECTION;
} else {
throw new IllegalStateException("No code loading strategy available");
}
} catch (Exception ex) {
throw new SystemException(ex);
}
return strategy;
}
public void runAnonymous(T target, T anonymous) {
runAnonymous(null, target, anonymous);
}
/**
* Trying to emulate Groovy's with block. The constructor must be a no-arg.
* X x = ...;
* runAnonymous(x, new X() {{
* setName(...);
* setInt(...);
* }}
*
* setName and setInt would be called on x.
*/
public void runAnonymous(MethodHandles.Lookup lookup, T target, T anonymous) {
Class anonymousClass = anonymous.getClass();
try {
AnonymousDescriptor descriptor = anonymousClasses.get(anonymousClass, () -> {
ClassLoadingStrategy strategy;
Class> enclosingClass = anonymousClass.getEnclosingClass();
if (lookup == null && enclosingClass != null && ClassInjector.UsingLookup.isAvailable() && Modifier.isPublic(enclosingClass.getModifiers())) {
Class> methodHandles = Class.forName("java.lang.invoke.MethodHandles");
Method lookupMethod = methodHandles.getMethod("lookup");
DynamicType.Unloaded unloaded = new ByteBuddy()
.subclass(enclosingClass)
.defineMethod("_methodHandlesLookup", MethodHandles.Lookup.class, Modifier.PUBLIC ^ Modifier.STATIC)
.intercept(MethodCall.invoke(lookupMethod))
.make();
Class enclosingClass2 = loadClass(unloaded, enclosingClass, null);
Method enclosingLookup = enclosingClass2.getMethod("_methodHandlesLookup");
MethodHandles.Lookup lookup2 = (MethodHandles.Lookup) enclosingLookup.invoke(null);
strategy = getClassLoadingStrategy(lookup2, anonymousClass);
} else {
strategy = getClassLoadingStrategy(lookup, anonymousClass);
}
DynamicType.Builder builder = newSingletonBuilder((Class) anonymousClass);
Class p;
p = builder.make()
.load(anonymousClass.getClassLoader(), strategy)
.getLoaded();
Field field = p.getDeclaredField(HANDLER_FIELD);
field.setAccessible(true);
InterceptorHandlerWrapper wrapper = new InterceptorHandlerWrapper(this,
p,
null,
null,
ctx -> { // the use of anonymousThreadLocal is necessary because the wrapper is only created once and cached.
Throwable th = new Throwable();
StackTraceElement[] elements = th.getStackTrace();
if (elements[3].getClassName().equals(anonymousClass.getName())) { // only for calls made within the anonymous class
Object t = anonymousThreadLocal.get();
return ctx.invoke(t);
} else {
return Primitives.defaultValue(ctx.getMethod().getReturnType());
}
},
null,
null);
field.set(null, wrapper);
Field[] fields = anonymousClass.getDeclaredFields();
AnonymousDescriptor desc = new AnonymousDescriptor();
Field[] copy = new Field[fields.length];
copy[0] = fields[fields.length - 1];
System.arraycopy(fields, 0, copy, 1,fields.length - 1);
desc.fields = copy;
for (Field f : desc.fields)
f.setAccessible(true);
desc.classes = new Class[copy.length];
for (int i = 0; i < copy.length; i++)
desc.classes[i] = copy[i].getType();
desc.constructor = p.getDeclaredConstructor(desc.classes);
return desc;
});
anonymousThreadLocal.set(target);
descriptor.construct(anonymous);
} catch (Exception e) {
throw new SystemException(e);
} finally {
anonymousThreadLocal.remove();
}
}
private static final class AnonymousDescriptor {
Field[] fields;
Class[] classes;
Constructor constructor;
final void construct(final Object anonymous) throws IllegalAccessException, InvocationTargetException, InstantiationException {
Object[] values = new Object[fields.length];
for (int i = 0; i < values.length; i++)
values[i] = fields[i].get(anonymous);
constructor.newInstance(values);
}
}
public InterceptorBuilder interceptorBuilder(T instance, InterceptorHandler handler) {
return new InterceptorBuilder<>(this, instance, handler);
}
/**
* Creates an interceptor for an instance. All calls for the interceptor, except for methods declared in Object, are
* forwarded to the handler.
* @param instance the instance of which calls are to be intercepted
* @param handler handler of intercepted calls
* @param type of instance
* @return an instance of interceptor
*/
public T newInterceptor(T instance, InterceptorHandler handler) {
return interceptorBuilder(instance, handler).build();
}
public InstanceBuilder instanceBuilder(Class cls, InterceptorHandler handler) {
return new InstanceBuilder<>(this, cls, handler);
}
/**
* Create an interceptor just like the newInterceptor method. However, the instance is set to cls.newInstance().
* @param cls instance class
* @param handler handler
* @param type of instance
* @return byte enhanced instance.
*/
public T newInstance(Class cls, InterceptorHandler handler) {
return instanceBuilder(cls, handler).build();
}
Class createInstanceClass(Class cls, ClassLoader classLoader) {
try {
return proxyClasses.get(cls, () -> {
DynamicType.Unloaded unloaded =
new ByteBuddy()
.subclass(cls)
.method(ElementMatchers.any().and(ElementMatchers.not(ElementMatchers.isDeclaredBy(Object.class))))
.intercept(MethodDelegation.toField(HANDLER_FIELD))
.defineField(HANDLER_FIELD, Handler.class, Visibility.PRIVATE)
.implement(HandlerAccessor.class).intercept(FieldAccessor.ofBeanProperty())
.make();
return loadClass(unloaded, cls, classLoader);
});
} catch (ExecutionException e) {
throw new SystemException(e.getCause());
}
}
public static boolean isProxyObject(Object proxyObject) {
return proxyObject instanceof HandlerAccessor;
}
public static T cloneProxyObject(T proxyObject) {
if (!(proxyObject instanceof HandlerAccessor)) {
throw new IllegalArgumentException("argument is not a proxy object");
}
InterceptorHandlerWrapper wrapper = (InterceptorHandlerWrapper) ((HandlerAccessor) proxyObject).getHandler();
wrapper = new InterceptorHandlerWrapper(wrapper); // make a copy
T cloneProxy = (T) wrapper.newObject.newObject(wrapper.proxyClass);
((HandlerAccessor) cloneProxy).setHandler(wrapper);
return cloneProxy;
}
public static T getTarget(T proxyObject) {
InterceptorHandlerWrapper wrapper = (InterceptorHandlerWrapper) ((HandlerAccessor) proxyObject).getHandler();
return (T) wrapper.target;
}
public static void setTarget(T proxyObject, T target) {
InterceptorHandlerWrapper wrapper = (InterceptorHandlerWrapper) ((HandlerAccessor) proxyObject).getHandler();
if (target != null && !target.getClass().isAssignableFrom(wrapper.targetClass)) {
throw new IllegalArgumentException("Target class " + target.getClass() + " is not assignable from " + wrapper.targetClass);
}
wrapper.target = target;
}
public static Class getTargetClass(Object proxyObject) {
InterceptorHandlerWrapper wrapper = (InterceptorHandlerWrapper) ((HandlerAccessor) proxyObject).getHandler();
return wrapper.targetClass;
}
public static T getInterceptorHandler(Object proxyObject) {
InterceptorHandlerWrapper wrapper = (InterceptorHandlerWrapper) ((HandlerAccessor) proxyObject).getHandler();
return (T) wrapper.handler;
}
public static void setInterceptorHandler(Object proxyObject, T handler) {
InterceptorHandlerWrapper wrapper = (InterceptorHandlerWrapper) ((HandlerAccessor) proxyObject).getHandler();
wrapper.handler = handler;
}
public static T getInterceptorListener(Object proxyObject) {
InterceptorHandlerWrapper wrapper = (InterceptorHandlerWrapper) ((HandlerAccessor) proxyObject).getHandler();
return (T) wrapper.listener;
}
public static void setInterceptorListener(Object proxyObject, T handler) {
InterceptorHandlerWrapper wrapper = (InterceptorHandlerWrapper) ((HandlerAccessor) proxyObject).getHandler();
wrapper.listener = handler;
}
@SuppressWarnings("squid:S00112")
@FunctionalInterface
public interface Handler {
@RuntimeType
Object handle(@Origin MethodHandle methodHandler, @Origin Method method, @AllArguments() Object[] arguments) throws Throwable;
}
/*
* Implemented by an interceptor class so that handler can be set.
*/
public interface HandlerAccessor {
Handler getHandler();
void setHandler(Handler handler);
}
static class InterceptorHandlerWrapper implements Handler {
private static ObjectPool objectPool = new ObjectPool().factory(CallFrame::new).build();
InterceptorHandler handler;
InterceptorListener listener;
Object proxyObject;
Object target;
Class targetClass;
Class proxyClass;
Interceptor interceptor;
NewObject newObject;
InterceptorHandlerWrapper(Interceptor interceptor,
Class proxyClass,
Object proxyObject,
Object target,
InterceptorHandler handler,
InterceptorListener listener,
NewObject newObject) {
this.interceptor = interceptor;
this.proxyClass = proxyClass;
this.handler = handler;
this.listener = listener;
this.proxyObject = proxyObject;
this.target = target;
if (target != null)
targetClass = target.getClass();
this.newObject = newObject;
if (this.newObject == null)
this.newObject = AbstractBuilder.defaultNewObject;
}
InterceptorHandlerWrapper(InterceptorHandlerWrapper copy) {
this.interceptor = copy.interceptor;
this.proxyClass = copy.proxyClass;
this.handler = copy.handler;
this.listener = copy.listener;
this.proxyObject = copy.proxyObject;
this.target = copy.target;
this.targetClass = copy.targetClass;
this.newObject = copy.newObject;
}
public Object handle(MethodHandle methodHandle, Method method, @RuntimeType Object[] arguments) throws Throwable {
CallFrame frame = null;
try {
frame = objectPool.checkOut();
frame.initialize(proxyObject, target, methodHandle, method, arguments);
if (listener != null)
listener.preInvocation(frame);
Object ret;
try {
ret = handler.invoke(frame);
} catch (Throwable throwable) {
if (listener != null)
return listener.onException(frame, throwable);
else throw throwable;
}
if (listener != null)
ret = listener.postInvocation(frame, ret);
return ret;
} finally {
if (frame != null) {
frame.clear();
objectPool.checkIn(frame);
}
}
}
}
}