com.thoughtworks.paranamer.JavaFileParanamer Maven / Gradle / Ivy
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
}
}
}
}
}