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

com.thoughtworks.paranamer.JavaFileParanamer Maven / Gradle / Ivy

There is a newer version: 0.63
Show newest version
package com.thoughtworks.paranamer;

import japa.parser.JavaParser;
import japa.parser.ast.CompilationUnit;
import japa.parser.ast.PackageDeclaration;
import japa.parser.ast.body.ClassOrInterfaceDeclaration;
import japa.parser.ast.body.ConstructorDeclaration;
import japa.parser.ast.body.MethodDeclaration;
import japa.parser.ast.body.Parameter;
import japa.parser.ast.visitor.GenericVisitor;
import japa.parser.ast.visitor.GenericVisitorAdapter;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * .java parser implementation of Paranamer. It relies on javaparser library 1.0.8+ to parse java files
 * retrieved by a {@link JavaFileFinder} implementation.
 * 
 * @author Gael Lazzari
 * 
 */
public class JavaFileParanamer implements Paranamer {

   /**
    * Interface in charge of retrieving .java file where a specific {@link Method} or
    * {@link Constructor} is declared.
    * 
    * @author Gael Lazzari
    * 
    */
   public interface JavaFileFinder {

      /**
       * 
       * Retrieve and open the .java file where the specified {@link Method} or {@link Constructor}
       * is declared
       * 
       * @param methodOrConstructor the {@link Method} or {@link Constructor} for which the
       *           parameter names are looked up.
       * @return an opened input stream on the corresponding .java file, or null if not found.
       */
      InputStream openJavaFile(AccessibleObject methodOrConstructor);

   }

   /**
    * Paranamer internal implementation of javaparser {@link GenericVisitor}.
    * 
    * @author Gael Lazzari
    * 
    */
   private static class MethodParametersVisitor extends
            GenericVisitorAdapter> {

      private final StringBuilder currentClassName = new StringBuilder();
      private String packageName;

      @Override
      public Void visit(ClassOrInterfaceDeclaration n, Map arg) {

         currentClassName.append("$").append(n.getName());

         super.visit(n, arg);

         currentClassName.delete(currentClassName.length() - n.getName().length() - 1,
                  currentClassName.length());

         return null;
      }

      @Override
      public Void visit(ConstructorDeclaration n, Map arg) {
         Constructor c = findConstructor(n);

         String[] paramNames = extractParameterNames(n.getParameters());
         arg.put(c, paramNames);

         return super.visit(n, arg);

      }

      @Override
      public Void visit(MethodDeclaration n, Map arg) {

         Method m = findMethod(n);
         String[] paramNames = extractParameterNames(n.getParameters());
         arg.put(m, paramNames);

         return super.visit(n, arg);
      }

      @Override
      public Void visit(PackageDeclaration n, Map arg) {

         packageName = n.getName().toString();
         return super.visit(n, arg);
      }

      private boolean argsMatch(Class[] parameterTypes, List parameters) {

         if (parameters == null) {
            return parameterTypes.length == 0;
         } else if (parameters.size() != parameterTypes.length) {
            return false;
         }

         for (int i = 0; i < parameterTypes.length; i++) {
            Class paramType = parameterTypes[i];
            Parameter param = parameters.get(i);

            if (!paramType.getSimpleName().equals(param.getType().toString())) {
               return false;
            }
         }

         return true;
      }

      private String[] extractParameterNames(List parameters) {
         int length = (parameters == null) ? 0 : parameters.size();
         String[] params = new String[length];

         for (int i = 0; i < params.length; i++) {
            params[i] = parameters.get(i).getId().getName();
         }

         return params;
      }

      private Constructor findConstructor(ConstructorDeclaration n) {
         Class currentVisitedClass = getCurrentVisitedClass();

         for (Constructor constructor : currentVisitedClass.getDeclaredConstructors()) {
            if (argsMatch(constructor.getParameterTypes(), n.getParameters())) {
               return constructor;
            }
         }

         return null;
      }

      private Method findMethod(MethodDeclaration n) {
         Class currentVisitedClass = getCurrentVisitedClass();

         for (Method method : currentVisitedClass.getDeclaredMethods()) {
            if (!method.getName().equals(n.getName())) {
               continue;
            } else if (argsMatch(method.getParameterTypes(), n.getParameters())) {
               return method;
            }
         }

         return null;
      }

      private Class getCurrentVisitedClass() {
         String className = packageName + "." + currentClassName.substring(1);

         try {
            return Class.forName(className);
         } catch (ClassNotFoundException e) {
            throw new ParameterNamesNotFoundException("Error while trying to retrieve class "
                     + className + " :", e);
         }
      };

   }

   private final Map cache;
   private final JavaFileFinder javaFileFinder;

   /**
    * 
    * @param javaFileFinder Object responsible for opening .java files where requested
    *           {@link Method} or {@link Constructor} are declared.
    */
   public JavaFileParanamer(JavaFileFinder javaFileFinder) {
      this.javaFileFinder = javaFileFinder;
      this.cache = new HashMap();
   }

   /*
    * (non-Javadoc)
    * 
    * @see com.thoughtworks.paranamer.Paranamer#lookupParameterNames(java.lang.reflect
    * .AccessibleObject)
    */
   public String[] lookupParameterNames(AccessibleObject methodOrConstructor) {
      return lookupParameterNames(methodOrConstructor, true);
   }

   /*
    * (non-Javadoc)
    * 
    * @see com.thoughtworks.paranamer.Paranamer#lookupParameterNames(java.lang.reflect
    * .AccessibleObject, boolean)
    */
   public String[] lookupParameterNames(AccessibleObject methodOrConstructor,
            boolean throwExceptionIfMissing) {

      if (methodOrConstructor == null) {
         throw new NullPointerException("method or constructor to inspect cannot be null");
      }

      String[] result = cache.get(methodOrConstructor);

      if (result == null) {
         visitJavaFileToPopulateCache(methodOrConstructor);
         result = cache.get(methodOrConstructor);
      }

      if (result == null && throwExceptionIfMissing) {
         throw new ParameterNamesNotFoundException("Cannot retrieve parameter names for method "
                  + methodOrConstructor.toString());
      }

      return result;
   }

   private void visitJavaFileToPopulateCache(AccessibleObject methodOrConstructor) {

      InputStream is = null;
      try {
         is = javaFileFinder.openJavaFile(methodOrConstructor);
         if (is != null) {
            // visit .java file using our custom GenericVisitorAdapter
            CompilationUnit cu = JavaParser.parse(is);
            MethodParametersVisitor visitor = new MethodParametersVisitor();
            cu.accept(visitor, cache);
         }
      } catch (Exception e) {
         throw new ParameterNamesNotFoundException(
                  "Error while trying to read parameter names from the Java file which contains the declaration of "
                           + methodOrConstructor.toString(), e);
      } finally {
         if (is != null) {
            try {
               is.close();
            } catch (IOException e) {
               // should never happen
            }
         }
      }
   }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy