package com.github.ompc.greys.core.advisor;
import com.github.ompc.greys.core.Advice;
import com.github.ompc.greys.core.util.GaCheckUtils;
import com.github.ompc.greys.core.util.GaMethod;
import com.github.ompc.greys.core.util.LazyGet;
import com.github.ompc.greys.core.util.collection.GaStack;
import com.github.ompc.greys.core.util.collection.ThreadUnsafeGaStack;
import org.apache.commons.lang3.StringUtils;
import org.objectweb.asm.Type;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import static com.github.ompc.greys.core.Advice.*;
import static com.github.ompc.greys.core.util.GaStringUtils.tranClassName;
/**
* 反射通知适配器<br/>
* 通过反射拿到对应的Class/Method类,而不是原始的ClassName/MethodNam
* 当然性能开销要比普通监听器高许多
*/
public abstract class ReflectAdviceListenerAdapter extends AdviceListenerAdapter {
@Override
public void create() {
}
@Override
public void destroy() {
}
private ClassLoader toClassLoader(ClassLoader loader) {
return null != loader
? loader
: AdviceListener.class.getClassLoader();
}
private Class<?> toClass(ClassLoader loader, String className) throws ClassNotFoundException {
return Class.forName(tranClassName(className), true, toClassLoader(loader));
}
private GaMethod toMethod(ClassLoader loader, Class<?> clazz, String methodName, String methodDesc)
throws ClassNotFoundException, NoSuchMethodException {
final org.objectweb.asm.Type asmType = org.objectweb.asm.Type.getMethodType(methodDesc);
// to arg types
final Class<?>[] argsClasses = new Class<?>[asmType.getArgumentTypes().length];
for (int index = 0; index < argsClasses.length; index++) {
// asm class descriptor to jvm class
final Class<?> argumentClass;
final Type argumentAsmType = asmType.getArgumentTypes()[index];
switch (argumentAsmType.getSort()) {
case Type.BOOLEAN: {
argumentClass = boolean.class;
break;
}
case Type.CHAR: {
argumentClass = char.class;
break;
}
case Type.BYTE: {
argumentClass = byte.class;
break;
}
case Type.SHORT: {
argumentClass = short.class;
break;
}
case Type.INT: {
argumentClass = int.class;
break;
}
case Type.FLOAT: {
argumentClass = float.class;
break;
}
case Type.LONG: {
argumentClass = long.class;
break;
}
case Type.DOUBLE: {
argumentClass = double.class;
break;
}
case Type.ARRAY: {
argumentClass = toClass(loader, argumentAsmType.getInternalName());
break;
}
case Type.VOID: {
argumentClass = void.class;
break;
}
case Type.OBJECT:
case Type.METHOD:
default: {
argumentClass = toClass(loader, argumentAsmType.getClassName());
break;
}
}
argsClasses[index] = argumentClass;
}
// to method or constructor
if (GaCheckUtils.isEquals(methodName, "<init>")) {
return new GaMethod.ConstructorImpl(toConstructor(clazz, argsClasses));
} else {
return new GaMethod.MethodImpl(toMethod(clazz, methodName, argsClasses));
}
}
private Method toMethod(Class<?> clazz, String methodName, Class<?>[] argClasses) throws NoSuchMethodException {
return clazz.getDeclaredMethod(methodName, argClasses);
}
private Constructor<?> toConstructor(Class<?> clazz, Class<?>[] argClasses) throws NoSuchMethodException {
return clazz.getDeclaredConstructor(argClasses);
}
private LazyGet<Class<?>> toClassRef(final ClassLoader loader, final String className) {
return new LazyGet<Class<?>>() {
@Override
protected Class<?> initialValue() throws Throwable {
return toClass(loader, className);
}
};
}
private LazyGet<GaMethod> toMethodRef(final ClassLoader loader, final LazyGet<Class<?>> clazzRef, final String methodName, final String methodDesc) {
return new LazyGet<GaMethod>() {
@Override
protected GaMethod initialValue() throws Throwable {
return toMethod(loader, clazzRef.get(), methodName, methodDesc);
}
};
}
private final ThreadLocal<GaStack<LazyGet<?>>> infoStackRef = new ThreadLocal<GaStack<LazyGet<?>>>() {
@Override
protected GaStack<LazyGet<?>> initialValue() {
return new ThreadUnsafeGaStack<LazyGet<?>>();
}
};
@Override
final public void before(
ClassLoader loader, String className, String methodName, String methodDesc,
Object target, Object[] args) throws Throwable {
try {
final LazyGet<Class<?>> clazzRef = toClassRef(loader, className);
final LazyGet<GaMethod> methodRef = toMethodRef(loader, clazzRef, methodName, methodDesc);
final GaStack<LazyGet<?>> infoStack = infoStackRef.get();
infoStack.push(clazzRef);
infoStack.push(methodRef);
before(newForBefore(loader, clazzRef, methodRef, target, args));
} finally {
beforeHook();
}
}
/**
* before()回调钩子<br/>
* 用来提供给 {@link ReflectAdviceTracingListenerAdapter 修正#78问题}
*/
void beforeHook() {
}
/**
* finish()回调钩子<br/>
* 用来提供给 {@link ReflectAdviceTracingListenerAdapter 修正#78问题}
*/
void finishHook() {
}
@Override
final public void afterReturning(
ClassLoader loader, String className, String methodName, String methodDesc,
Object target, Object[] args, Object returnObject) throws Throwable {
try {
final GaStack<LazyGet<?>> infoStack = infoStackRef.get();
final LazyGet<GaMethod> methodRef = (LazyGet<GaMethod>) infoStack.pop();
final LazyGet<Class<?>> clazzRef = (LazyGet<Class<?>>) infoStack.pop();
final Advice advice = newForAfterRetuning(
loader,
clazzRef,
methodRef,
target,
args,
// #98 在return的时候,如果目标函数是<init>,会导致return的内容缺失
// 初步的想法是用target(this)去代替returnObj
StringUtils.equals("<init>", methodName) ? target : returnObject
);
afterReturning(advice);
afterFinishing(advice);
} finally {
finishHook();
}
}
@Override
final public void afterThrowing(
ClassLoader loader, String className, String methodName, String methodDesc,
Object target, Object[] args, Throwable throwable) throws Throwable {
try {
final GaStack<LazyGet<?>> infoStack = infoStackRef.get();
final LazyGet<GaMethod> methodRef = (LazyGet<GaMethod>) infoStack.pop();
final LazyGet<Class<?>> clazzRef = (LazyGet<Class<?>>) infoStack.pop();
final Advice advice = newForAfterThrowing(
loader,
clazzRef,
methodRef,
target,
args,
throwable
);
afterThrowing(advice);
afterFinishing(advice);
} finally {
finishHook();
}
}
/**
* 前置通知
*
* @param advice 通知点
* @throws Throwable 通知过程出错
*/
public void before(Advice advice) throws Throwable {
}
/**
* 返回通知
*
* @param advice 通知点
* @throws Throwable 通知过程出错
*/
public void afterReturning(Advice advice) throws Throwable {
}
/**
* 异常通知
*
* @param advice 通知点
* @throws Throwable 通知过程出错
*/
public void afterThrowing(Advice advice) throws Throwable {
}
/**
* 结束通知
*
* @param advice 通知点
* @throws Throwable 通知过程出错
*/
public void afterFinishing(Advice advice) throws Throwable {
}
}