Make typeIs XPath functions use the typeres cache
This commit is contained in:
@ -60,6 +60,11 @@ public class ASTCompilationUnit extends AbstractJavaTypeNode implements RootNode
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ASTCompilationUnit getRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the package name of this compilation unit. If this is in
|
||||
* the default package, returns the empty string.
|
||||
|
@ -15,6 +15,7 @@ public abstract class AbstractJavaNode extends AbstractJjtreeNode<JavaNode> impl
|
||||
protected JavaParser parser;
|
||||
private Scope scope;
|
||||
private Comment comment;
|
||||
private ASTCompilationUnit root;
|
||||
|
||||
@InternalApi
|
||||
@Deprecated
|
||||
@ -64,6 +65,14 @@ public abstract class AbstractJavaNode extends AbstractJjtreeNode<JavaNode> impl
|
||||
return data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ASTCompilationUnit getRoot() {
|
||||
if (root == null) {
|
||||
root = getParent().getRoot();
|
||||
}
|
||||
return root;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Scope getScope() {
|
||||
if (scope == null) {
|
||||
|
@ -41,6 +41,7 @@ public interface JavaNode extends ScopedNode {
|
||||
@Deprecated
|
||||
Object childrenAccept(JavaParserVisitor visitor, Object data);
|
||||
|
||||
ASTCompilationUnit getRoot();
|
||||
|
||||
@Override
|
||||
JavaNode getChild(int index);
|
||||
|
@ -66,7 +66,7 @@ public class DuplicateImportsRule extends AbstractJavaRule {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
Class<?> importClass = node.getClassTypeResolver().loadClass(thisImportOnDemand.getName());
|
||||
Class<?> importClass = node.getClassTypeResolver().loadClassOrNull(thisImportOnDemand.getName());
|
||||
if (importClass != null) {
|
||||
for (Method m : importClass.getMethods()) {
|
||||
if (Modifier.isStatic(m.getModifiers()) && m.getName().equals(singleTypeName)) {
|
||||
|
@ -99,6 +99,7 @@ import net.sourceforge.pmd.lang.java.ast.JavaParserVisitorAdapter;
|
||||
import net.sourceforge.pmd.lang.java.ast.TypeNode;
|
||||
import net.sourceforge.pmd.lang.java.symboltable.ClassScope;
|
||||
import net.sourceforge.pmd.lang.java.symboltable.VariableNameDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.typeresolution.internal.NullableClassLoader;
|
||||
import net.sourceforge.pmd.lang.java.typeresolution.typedefinition.JavaTypeDefinition;
|
||||
import net.sourceforge.pmd.lang.symboltable.NameOccurrence;
|
||||
import net.sourceforge.pmd.lang.symboltable.Scope;
|
||||
@ -112,7 +113,7 @@ import net.sourceforge.pmd.lang.symboltable.Scope;
|
||||
|
||||
@Deprecated
|
||||
@InternalApi
|
||||
public class ClassTypeResolver extends JavaParserVisitorAdapter {
|
||||
public class ClassTypeResolver extends JavaParserVisitorAdapter implements NullableClassLoader {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(ClassTypeResolver.class.getName());
|
||||
|
||||
@ -1487,10 +1488,15 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter {
|
||||
return pmdClassLoader.loadClassOrNull(fullyQualifiedClassName) != null;
|
||||
}
|
||||
|
||||
public Class<?> loadClass(String fullyQualifiedClassName) {
|
||||
@Override
|
||||
public Class<?> loadClassOrNull(String fullyQualifiedClassName) {
|
||||
return pmdClassLoader.loadClassOrNull(fullyQualifiedClassName);
|
||||
}
|
||||
|
||||
public Class<?> loadClass(String fullyQualifiedClassName) {
|
||||
return loadClassOrNull(fullyQualifiedClassName);
|
||||
}
|
||||
|
||||
private Class<?> processOnDemand(String qualifiedName) {
|
||||
for (String entry : importedOnDemand) {
|
||||
String fullClassName = entry + "." + qualifiedName;
|
||||
@ -1533,12 +1539,12 @@ public class ClassTypeResolver extends JavaParserVisitorAdapter {
|
||||
String strPackage = anImportDeclaration.getPackageName();
|
||||
if (anImportDeclaration.isStatic()) {
|
||||
if (anImportDeclaration.isImportOnDemand()) {
|
||||
importOnDemandStaticClasses.add(JavaTypeDefinition.forClass(loadClass(strPackage)));
|
||||
importOnDemandStaticClasses.add(JavaTypeDefinition.forClass(loadClassOrNull(strPackage)));
|
||||
} else { // not import on-demand
|
||||
String strName = anImportDeclaration.getImportedName();
|
||||
String fieldName = strName.substring(strName.lastIndexOf('.') + 1);
|
||||
|
||||
Class<?> staticClassWithField = loadClass(strPackage);
|
||||
Class<?> staticClassWithField = loadClassOrNull(strPackage);
|
||||
if (staticClassWithField != null) {
|
||||
JavaTypeDefinition typeDef = getFieldType(JavaTypeDefinition.forClass(staticClassWithField),
|
||||
fieldName, currentAcu.getType());
|
||||
|
@ -4,7 +4,12 @@
|
||||
|
||||
package net.sourceforge.pmd.lang.java.typeresolution;
|
||||
|
||||
import org.apache.commons.lang3.ClassUtils;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTAnnotationTypeDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTClassOrInterfaceDeclaration;
|
||||
@ -13,9 +18,48 @@ import net.sourceforge.pmd.lang.java.ast.ASTEnumDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.ast.ASTImplementsList;
|
||||
import net.sourceforge.pmd.lang.java.ast.TypeNode;
|
||||
import net.sourceforge.pmd.lang.java.symboltable.TypedNameDeclaration;
|
||||
import net.sourceforge.pmd.lang.java.typeresolution.internal.NullableClassLoader;
|
||||
import net.sourceforge.pmd.lang.java.typeresolution.internal.NullableClassLoader.ClassLoaderWrapper;
|
||||
|
||||
public final class TypeHelper {
|
||||
|
||||
|
||||
/** Maps a primitive class name to its corresponding abbreviation used in array class names. */
|
||||
private static final Map<String, String> abbreviationMap;
|
||||
/** Maps names of primitives to their corresponding primitive {@code Class}es. */
|
||||
private static final Map<String, Class<?>> namePrimitiveMap = new HashMap<>();
|
||||
|
||||
|
||||
static {
|
||||
namePrimitiveMap.put("boolean", Boolean.TYPE);
|
||||
namePrimitiveMap.put("byte", Byte.TYPE);
|
||||
namePrimitiveMap.put("char", Character.TYPE);
|
||||
namePrimitiveMap.put("short", Short.TYPE);
|
||||
namePrimitiveMap.put("int", Integer.TYPE);
|
||||
namePrimitiveMap.put("long", Long.TYPE);
|
||||
namePrimitiveMap.put("double", Double.TYPE);
|
||||
namePrimitiveMap.put("float", Float.TYPE);
|
||||
namePrimitiveMap.put("void", Void.TYPE);
|
||||
}
|
||||
|
||||
static {
|
||||
final Map<String, String> m = new HashMap<>();
|
||||
m.put("int", "I");
|
||||
m.put("boolean", "Z");
|
||||
m.put("float", "F");
|
||||
m.put("long", "J");
|
||||
m.put("short", "S");
|
||||
m.put("byte", "B");
|
||||
m.put("double", "D");
|
||||
m.put("char", "C");
|
||||
final Map<String, String> r = new HashMap<>();
|
||||
for (final Map.Entry<String, String> e : m.entrySet()) {
|
||||
r.put(e.getValue(), e.getKey());
|
||||
}
|
||||
abbreviationMap = Collections.unmodifiableMap(m);
|
||||
}
|
||||
|
||||
|
||||
private TypeHelper() {
|
||||
// utility class
|
||||
}
|
||||
@ -101,34 +145,56 @@ public final class TypeHelper {
|
||||
|
||||
private static Class<?> loadClassWithNodeClassloader(final TypeNode n, final String clazzName) {
|
||||
if (n.getType() != null) {
|
||||
return loadClass(n.getType().getClassLoader(), clazzName);
|
||||
return loadClass(n.getRoot().getClassTypeResolver(), clazzName);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Class<?> loadClass(final ClassLoader nullableClassLoader, final String clazzName) {
|
||||
try {
|
||||
ClassLoader classLoader = nullableClassLoader;
|
||||
if (classLoader == null) {
|
||||
// Using the system classloader then
|
||||
classLoader = ClassLoader.getSystemClassLoader();
|
||||
private static Class<?> loadClass(NullableClassLoader ctr, String className) {
|
||||
Class<?> clazz;
|
||||
if (namePrimitiveMap.containsKey(className)) {
|
||||
clazz = namePrimitiveMap.get(className);
|
||||
} else {
|
||||
clazz = ctr.loadClassOrNull(toCanonicalName(className));
|
||||
}
|
||||
if (clazz != null) {
|
||||
return clazz;
|
||||
}
|
||||
|
||||
// allow path separators (.) as inner class name separators
|
||||
final int lastDotIndex = className.lastIndexOf('.');
|
||||
|
||||
if (lastDotIndex != -1) {
|
||||
String asInner = className.substring(0, lastDotIndex)
|
||||
+ '$' + className.substring(lastDotIndex + 1);
|
||||
return loadClass(ctr, asInner);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private static String toCanonicalName(String className) {
|
||||
className = StringUtils.deleteWhitespace(className);
|
||||
Validate.notNull(className, "className must not be null.");
|
||||
if (className.endsWith("[]")) {
|
||||
final StringBuilder classNameBuffer = new StringBuilder();
|
||||
while (className.endsWith("[]")) {
|
||||
className = className.substring(0, className.length() - 2);
|
||||
classNameBuffer.append("[");
|
||||
}
|
||||
|
||||
// If the requested type is in the classpath, using the same classloader should work
|
||||
return ClassUtils.getClass(classLoader, clazzName);
|
||||
} catch (ClassNotFoundException ignored) {
|
||||
// The requested type is not on the auxclasspath. This might happen, if the type node
|
||||
// is probed for a specific type (e.g. is is a JUnit5 Test Annotation class).
|
||||
// Failing to resolve clazzName does not necessarily indicate an incomplete auxclasspath.
|
||||
} catch (final LinkageError expected) {
|
||||
// We found the class but it's invalid / incomplete. This may be an incomplete auxclasspath
|
||||
// if it was a NoClassDefFoundError. TODO : Report it?
|
||||
final String abbreviation = abbreviationMap.get(className);
|
||||
if (abbreviation != null) {
|
||||
classNameBuffer.append(abbreviation);
|
||||
} else {
|
||||
classNameBuffer.append("L").append(className).append(";");
|
||||
}
|
||||
className = classNameBuffer.toString();
|
||||
}
|
||||
|
||||
return null;
|
||||
return className;
|
||||
}
|
||||
|
||||
|
||||
/** @see #isA(TypeNode, String) */
|
||||
public static boolean isA(TypeNode n, Class<?> clazz) {
|
||||
return subclasses(n, clazz);
|
||||
@ -142,7 +208,7 @@ public final class TypeHelper {
|
||||
Class<?> type = vnd.getType();
|
||||
for (final Class<?> clazz : clazzes) {
|
||||
if (type != null && type.equals(clazz) || type == null
|
||||
&& (clazz.getSimpleName().equals(vnd.getTypeImage()) || clazz.getName().equals(vnd.getTypeImage()))) {
|
||||
&& (clazz.getSimpleName().equals(vnd.getTypeImage()) || clazz.getName().equals(vnd.getTypeImage()))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -192,7 +258,7 @@ public final class TypeHelper {
|
||||
public static boolean isA(TypedNameDeclaration vnd, String className) {
|
||||
Class<?> type = vnd.getType();
|
||||
if (type != null) {
|
||||
Class<?> clazz = loadClass(type.getClassLoader(), className);
|
||||
Class<?> clazz = loadClass(new ClassLoaderWrapper(type.getClassLoader()), className);
|
||||
if (clazz != null) {
|
||||
return clazz.isAssignableFrom(type);
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
|
||||
*/
|
||||
|
||||
package net.sourceforge.pmd.lang.java.typeresolution.internal;
|
||||
|
||||
public interface NullableClassLoader {
|
||||
|
||||
Class<?> loadClassOrNull(String binaryName);
|
||||
|
||||
|
||||
class ClassLoaderWrapper implements NullableClassLoader {
|
||||
|
||||
private final ClassLoader classLoader;
|
||||
|
||||
public ClassLoaderWrapper(ClassLoader classLoader) {
|
||||
this.classLoader = classLoader;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> loadClassOrNull(String binaryName) {
|
||||
try {
|
||||
return classLoader.loadClass(binaryName);
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user