Document AttributeAxisIterator

This commit is contained in:
Clément Fournier
2018-06-10 03:00:45 +02:00
parent cb86f2a4b0
commit 3a8febb200

View File

@ -16,17 +16,36 @@ import java.util.concurrent.ConcurrentMap;
import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.Node;
/**
* Explores an AST node reflectively to iterate over its XPath
* attributes. This is the default way the attributes of a node
* are made accessible to XPath rules, and defines an important
* piece of PMD's XPath support.
*/
public class AttributeAxisIterator implements Iterator<Attribute> { public class AttributeAxisIterator implements Iterator<Attribute> {
/**
* Associates an attribute accessor with the XPath-accessible
* name of the attribute. This is used to avoid recomputing
* the name of the attribute for each attribute (it's only done
* once and put inside the {@link #METHOD_CACHE}.
*/
private static class MethodWrapper { private static class MethodWrapper {
public Method method; public Method method;
public String name; public String name;
MethodWrapper(Method m) { MethodWrapper(Method m) {
this.method = m; this.method = m;
this.name = truncateMethodName(m.getName()); this.name = truncateMethodName(m.getName());
} }
/**
* This method produces the actual XPath name of an attribute
* from the name of its accessor.
*/
private String truncateMethodName(String n) { private String truncateMethodName(String n) {
// about 70% of the methods start with 'get', so this case goes // about 70% of the methods start with 'get', so this case goes
// first // first
@ -52,12 +71,15 @@ public class AttributeAxisIterator implements Iterator<Attribute> {
private int position; private int position;
private Node node; private Node node;
private static ConcurrentMap<Class<?>, MethodWrapper[]> methodCache = private static final ConcurrentMap<Class<?>, MethodWrapper[]> METHOD_CACHE = new ConcurrentHashMap<>();
new ConcurrentHashMap<Class<?>, MethodWrapper[]>();
/**
* Creates a new iterator that enumerates the attributes of the given node.
*/
public AttributeAxisIterator(Node contextNode) { public AttributeAxisIterator(Node contextNode) {
this.node = contextNode; this.node = contextNode;
if (!methodCache.containsKey(contextNode.getClass())) { if (!METHOD_CACHE.containsKey(contextNode.getClass())) {
Method[] preFilter = contextNode.getClass().getMethods(); Method[] preFilter = contextNode.getClass().getMethods();
List<MethodWrapper> postFilter = new ArrayList<>(); List<MethodWrapper> postFilter = new ArrayList<>();
for (Method element : preFilter) { for (Method element : preFilter) {
@ -65,17 +87,18 @@ public class AttributeAxisIterator implements Iterator<Attribute> {
postFilter.add(new MethodWrapper(element)); postFilter.add(new MethodWrapper(element));
} }
} }
methodCache.putIfAbsent(contextNode.getClass(), postFilter.toArray(new MethodWrapper[0])); METHOD_CACHE.putIfAbsent(contextNode.getClass(), postFilter.toArray(new MethodWrapper[0]));
} }
this.methodWrappers = methodCache.get(contextNode.getClass()); this.methodWrappers = METHOD_CACHE.get(contextNode.getClass());
this.position = 0; this.position = 0;
this.currObj = getNextAttribute(); this.currObj = getNextAttribute();
} }
@Override @Override
public Attribute next() { public Attribute next() {
if (currObj == null) { if (!hasNext()) {
throw new IndexOutOfBoundsException(); throw new IndexOutOfBoundsException();
} }
Attribute ret = currObj; Attribute ret = currObj;
@ -83,16 +106,19 @@ public class AttributeAxisIterator implements Iterator<Attribute> {
return ret; return ret;
} }
@Override @Override
public boolean hasNext() { public boolean hasNext() {
return currObj != null; return currObj != null;
} }
@Override @Override
public void remove() { public void remove() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
private Attribute getNextAttribute() { private Attribute getNextAttribute() {
if (methodWrappers == null || position == methodWrappers.length) { if (methodWrappers == null || position == methodWrappers.length) {
return null; return null;
@ -102,20 +128,26 @@ public class AttributeAxisIterator implements Iterator<Attribute> {
} }
private static final Set<Class<?>> CONSIDERED_RETURN_TYPES private static final Set<Class<?>> CONSIDERED_RETURN_TYPES
= new HashSet<>(Arrays.<Class<?>>asList(Integer.TYPE, Boolean.TYPE, Double.TYPE, String.class, Long.TYPE, Character.TYPE, Float.TYPE)); = new HashSet<>(Arrays.<Class<?>>asList(Integer.TYPE, Boolean.TYPE, Double.TYPE, String.class, Long.TYPE, Character.TYPE, Float.TYPE));
private static final Set<String> FILTERED_OUT_NAMES private static final Set<String> FILTERED_OUT_NAMES
= new HashSet<>(Arrays.asList("toString", "getClass", "getXPathNodeName", "getTypeNameNode", "hashCode", "getImportedNameNode", "getScope")); = new HashSet<>(Arrays.asList("toString", "getClass", "getXPathNodeName", "getTypeNameNode", "hashCode", "getImportedNameNode", "getScope"));
/**
* Returns whether the given method is an attribute accessor,
* in which case a corresponding Attribute will be added to
* the iterator.
*
* @param method The method to test
*/
protected boolean isAttributeAccessor(Method method) { protected boolean isAttributeAccessor(Method method) {
String methodName = method.getName(); String methodName = method.getName();
return CONSIDERED_RETURN_TYPES.contains(method.getReturnType()) return CONSIDERED_RETURN_TYPES.contains(method.getReturnType())
&& method.getParameterTypes().length == 0 && method.getParameterTypes().length == 0
&& !methodName.startsWith("jjt") && !methodName.startsWith("jjt")
&& !FILTERED_OUT_NAMES.contains(methodName); && !FILTERED_OUT_NAMES.contains(methodName);
} }
} }