Add the attributes of the underlying node to Apex xpath nodes

This commit is contained in:
Clément Fournier
2019-01-09 17:50:03 +01:00
parent a0109aeef9
commit 9673d51fbf
7 changed files with 88 additions and 12 deletions

View File

@ -10,6 +10,7 @@ public class ASTBooleanExpression extends AbstractApexNode<BooleanExpression> {
public ASTBooleanExpression(BooleanExpression booleanExpression) {
super(booleanExpression);
booleanExpression.getOp();
}
@Override

View File

@ -4,7 +4,14 @@
package net.sourceforge.pmd.lang.apex.ast;
import java.util.Iterator;
import java.util.Spliterators;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import net.sourceforge.pmd.lang.ast.SourceCodePositioner;
import net.sourceforge.pmd.lang.ast.xpath.Attribute;
import net.sourceforge.pmd.lang.ast.xpath.AttributeAxisIterator;
import apex.jorje.data.Location;
import apex.jorje.data.Locations;
@ -57,4 +64,20 @@ public abstract class AbstractApexNode<T extends AstNode> extends AbstractApexNo
return "no location";
}
}
@Override
public Iterator<Attribute> getXPathAttributesIterator() {
// Attributes of this node have precedence over same-name attributes of the underlying node
return Stream.concat(iteratorToStream(new AttributeAxisIterator(this)),
iteratorToStream(new AttributeAxisIterator(this, node)))
.distinct()
.iterator();
}
private static <T> Stream<T> iteratorToStream(Iterator<? extends T> it) {
return StreamSupport.stream(Spliterators.spliteratorUnknownSize(it, 0), false);
}
}

View File

@ -6,6 +6,7 @@ package net.sourceforge.pmd.lang.ast.xpath;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
@ -18,6 +19,9 @@ import net.sourceforge.pmd.lang.ast.Node;
* Attributes know their name, the node they wrap,
* and have access to their value.
*
* Two attributes are equal iff their parent node is
* equal and they have the same name.
*
* @author daniels
*/
public class Attribute {
@ -28,8 +32,9 @@ public class Attribute {
private static final Object[] EMPTY_OBJ_ARRAY = new Object[0];
private Node parent;
private String name;
private final Node parent;
private final Object myBean;
private final String name;
private Method method;
private Object value;
private String stringValue;
@ -39,15 +44,25 @@ public class Attribute {
this.parent = parent;
this.name = name;
this.method = m;
this.myBean = parent;
}
/** Creates a new attribute belonging to the given node using its accessor. */
public Attribute(Node parent, Object bean, String name, Method m) {
this.parent = parent;
this.name = name;
this.method = m;
this.myBean = bean;
}
/** Creates a new attribute belonging to the given node using its string value. */
public Attribute(Node parent, String name, String value) {
this.parent = parent;
this.name = name;
this.value = value;
this.stringValue = value;
this.myBean = parent;
}
@ -73,7 +88,7 @@ public class Attribute {
// this lazy loading reduces calls to Method.invoke() by about 90%
try {
value = method.invoke(parent, EMPTY_OBJ_ARRAY);
value = method.invoke(myBean, EMPTY_OBJ_ARRAY);
return value;
} catch (IllegalAccessException | InvocationTargetException iae) {
iae.printStackTrace();
@ -93,12 +108,33 @@ public class Attribute {
return stringValue;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Attribute attribute = (Attribute) o;
return Objects.equals(parent, attribute.parent) &&
Objects.equals(name, attribute.name);
}
@Override
public int hashCode() {
return Objects.hash(parent, name);
}
private String getLoggableAttributeName() {
return parent.getXPathNodeName() + "/@" + name;
}
@Override
public String toString() {
return name + ':' + getValue() + ':' + parent;
return name + ':' + getValue() + ':' + parent.getXPathNodeName();
}
}

View File

@ -10,6 +10,7 @@ import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@ -44,6 +45,7 @@ public class AttributeAxisIterator implements Iterator<Attribute> {
private MethodWrapper[] methodWrappers;
private int position;
private Node node;
private final Object bean;
/**
@ -52,24 +54,28 @@ public class AttributeAxisIterator implements Iterator<Attribute> {
* use instead the overridable {@link Node#getXPathAttributesIterator()}.
*/
public AttributeAxisIterator(Node contextNode) {
this(contextNode, contextNode);
}
public AttributeAxisIterator(Node contextNode, Object bean) {
this.node = contextNode;
if (!METHOD_CACHE.containsKey(contextNode.getClass())) {
Method[] preFilter = contextNode.getClass().getMethods();
this.bean = bean;
if (!METHOD_CACHE.containsKey(bean.getClass())) {
Method[] preFilter = bean.getClass().getMethods();
List<MethodWrapper> postFilter = new ArrayList<>();
for (Method element : preFilter) {
if (isAttributeAccessor(element)) {
postFilter.add(new MethodWrapper(element));
}
}
METHOD_CACHE.putIfAbsent(contextNode.getClass(), postFilter.toArray(new MethodWrapper[0]));
METHOD_CACHE.putIfAbsent(bean.getClass(), postFilter.toArray(new MethodWrapper[0]));
}
this.methodWrappers = METHOD_CACHE.get(contextNode.getClass());
this.methodWrappers = METHOD_CACHE.get(bean.getClass());
this.position = 0;
this.currObj = getNextAttribute();
}
/**
* Returns whether the given method is an attribute accessor,
* in which case a corresponding Attribute will be added to
@ -80,12 +86,16 @@ public class AttributeAxisIterator implements Iterator<Attribute> {
protected boolean isAttributeAccessor(Method method) {
String methodName = method.getName();
return CONSIDERED_RETURN_TYPES.contains(method.getReturnType())
return isConsideredReturnType(method.getReturnType())
&& method.getParameterTypes().length == 0
&& !methodName.startsWith("jjt")
&& !FILTERED_OUT_NAMES.contains(methodName);
}
private boolean isConsideredReturnType(Class<?> klass) {
return CONSIDERED_RETURN_TYPES.contains(klass) || klass.isEnum();
}
@Override
public Attribute next() {
@ -115,7 +125,7 @@ public class AttributeAxisIterator implements Iterator<Attribute> {
return null;
}
MethodWrapper m = methodWrappers[position++];
return new Attribute(node, m.name, m.method);
return new Attribute(node, bean, m.name, m.method);
}

View File

@ -236,7 +236,9 @@ public class SaxonXPathRuleQuery extends AbstractXPathRuleQuery {
*/
if (value == null) {
return UntypedAtomicValue.ZERO_LENGTH_UNTYPED;
} else if (value instanceof Enum) {
// enums use their toString
return new StringValue(value.toString());
} else if (value instanceof String) {
return new StringValue((String) value);
} else if (value instanceof Boolean) {

View File

@ -164,6 +164,7 @@ public abstract class AbstractPropertySource implements PropertySource {
@Override
@Deprecated
public <V> void setProperty(MultiValuePropertyDescriptor<V> propertyDescriptor, V... values) {
checkValidPropertyDescriptor(propertyDescriptor);
propertyValuesByDescriptor.put(propertyDescriptor, Collections.unmodifiableList(Arrays.asList(values)));

View File

@ -112,7 +112,10 @@ public interface PropertySource {
* @param propertyDescriptor The property descriptor for which to add a value
* @param values Values
* @param <V> The type of the values
*
* @deprecated {@link MultiValuePropertyDescriptor} is deprecated
*/
@Deprecated
<V> void setProperty(MultiValuePropertyDescriptor<V> propertyDescriptor, V... values);