content: package com.fernandocejas.frodo2.logger.joinpoint;
  
  import org.aspectj.lang.JoinPoint;
  import org.aspectj.lang.Signature;
  import org.aspectj.lang.reflect.MethodSignature;
  
  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;
  
  /**
   * 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 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} 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();
    }
  }
  