package com.fernandocejas.frodo.joinpoint;
import android.os.Looper;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
/**
* Wrapper around {@link org.aspectj.lang.JoinPoint} to make easy retrieve data from a certain
* {@link org.aspectj.lang.JoinPoint} passed as a parameter in the constructor.
*/
public class FrodoJoinPoint {
private final JoinPoint joinPoint;
private final MethodSignature methodSignature;
private final String classCanonicalName;
private final String classSimpleName;
private final String methodName;
private final List<Class> methodParamTypesList;
private final List<String> methodParamNamesList;
private final List<Object> methodParamValuesList;
private final String executionThreadName;
private final String joinPointUniqueName;
/**
* Constructor of the class
*
* @param joinPoint object to wrap around.
*/
public FrodoJoinPoint(JoinPoint joinPoint) {
if (joinPoint == null) {
throw new IllegalArgumentException("Constructor parameters cannot be null!!!");
}
this.joinPoint = joinPoint;
this.methodSignature = (MethodSignature) this.joinPoint.getSignature();
this.classCanonicalName = this.methodSignature.getDeclaringType().getCanonicalName();
this.classSimpleName = this.methodSignature.getDeclaringType().getSimpleName();
this.methodName = this.methodSignature.getName();
this.executionThreadName = Thread.currentThread().getName();
final Class[] parameterTypes = this.methodSignature.getParameterTypes();
final String[] parameterNames = this.methodSignature.getParameterNames();
final Object[] args = this.joinPoint.getArgs();
this.methodParamTypesList = parameterTypes != null ?
Arrays.asList(parameterTypes) : Collections.<Class>emptyList();
this.methodParamNamesList = parameterNames != null ?
Arrays.asList(parameterNames) : Collections.<String>emptyList();
this.methodParamValuesList = args != null ? Arrays.asList(args) : Collections.emptyList();
this.joinPointUniqueName = this.generateJoinPointUniqueName();
}
public String getClassSimpleName() {
return classSimpleName;
}
public String getMethodName() {
return methodName;
}
public List<Class> getMethodParamTypesList() {
return methodParamTypesList;
}
public List<String> getMethodParamNamesList() {
return methodParamNamesList;
}
public List<Object> getMethodParamValuesList() {
return methodParamValuesList;
}
public String getExecutionThreadName() {
return executionThreadName;
}
/**
* Gets an annotation from a method, will return null in case it does not exist.
* It is important to have a retention policy of
* {@link java.lang.annotation.RetentionPolicy#RUNTIME} to avoid null values when reading them.
*
* @param annotation The {@link java.lang.annotation.Annotation} to get.
* @return The annotation if exists, otherwise null.
*/
public Annotation getAnnotation(Class<? extends Annotation> annotation) {
return methodSignature.getMethod().getAnnotation(annotation);
}
/**
* Check if the {@link JoinPoint} is being executed in the main thread.
*
* @return true: main thread, otherwise false.
*/
public boolean isMainThread() {
return (Looper.getMainLooper() == Looper.myLooper());
}
/**
* Check if the {@link JoinPoint} has a return type.
*
* @param joinPoint the {@link JoinPoint} to check.
* @return true if there is a return type, false if it is void.
*/
public boolean hasReturnType(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
return (signature instanceof MethodSignature
&& ((MethodSignature) signature).getReturnType() != void.class);
}
/**
* {@link MethodSignature#getReturnType()}.
*/
public Class getReturnType() {
return methodSignature.getReturnType();
}
/**
* {@link Method#getGenericReturnType()}.
*/
public List<Class> getGenericReturnTypes() {
final Type returnType = methodSignature.getMethod().getGenericReturnType();
if (returnType instanceof ParameterizedType) {
final Type[] typeArguments = ((ParameterizedType) returnType).getActualTypeArguments();
final List<Class> genericReturnTypes = new ArrayList<>(typeArguments.length);
for (Type typeArgument : typeArguments) {
genericReturnTypes.add(typeArgument.getClass());
}
return genericReturnTypes;
}
final Class clazz = (Class<?>) returnType;
return Collections.singletonList(clazz);
}
/**
* {@link JoinPoint#getTarget()}.
*/
public Object getTarget() {
return joinPoint.getTarget();
}
/**
* Get wrapped {@link JoinPoint}.
*/
public JoinPoint getJoinPoint() {
return joinPoint;
}
@Override
public boolean equals(Object object) {
if (object == this) {
return true;
}
if ((object == null) || (object.getClass() != this.getClass())) {
return false;
}
FrodoJoinPoint joinPoint = (FrodoJoinPoint) object;
return this.joinPointUniqueName.equals(joinPoint.joinPointUniqueName);
}
@Override
public int hashCode() {
return this.joinPointUniqueName.hashCode();
}
private String generateJoinPointUniqueName() {
StringBuilder stringBuilder = new StringBuilder(256);
stringBuilder.append(this.classCanonicalName);
stringBuilder.append(".");
stringBuilder.append(this.methodName);
stringBuilder.append("(");
if (!this.methodParamNamesList.isEmpty()) {
for (int i = 0; i < this.methodParamNamesList.size(); i++) {
stringBuilder.append(this.methodParamTypesList.get(i).getSimpleName());
stringBuilder.append(" ");
stringBuilder.append(this.methodParamNamesList.get(i));
if ((i != this.methodParamNamesList.size() - 1)) {
stringBuilder.append(", ");
}
}
}
stringBuilder.append(")");
return stringBuilder.toString();
}
}