[core] XPathHandler/XPathFunctionDefintion: Refactoring, javadoc

This commit is contained in:
Andreas Dangel 2024-01-25 13:57:30 +01:00
parent 721661c3f9
commit 304ff2ec25
No known key found for this signature in database
GPG Key ID: 93450DF2DF9A3FA3
8 changed files with 247 additions and 159 deletions

View File

@ -6,6 +6,8 @@ package net.sourceforge.pmd.lang.rule.xpath.impl;
import javax.xml.namespace.QName;
import org.checkerframework.checker.nullness.qual.Nullable;
import net.sourceforge.pmd.lang.Language;
import net.sourceforge.pmd.lang.ast.Node;
@ -35,37 +37,90 @@ public abstract class XPathFunctionDefinition {
return qname;
}
/**
* Defines the types of the function arguments. By default, an empty array is returned, indicating
* that the function takes no arguments.
*/
public Type[] getArgumentTypes() {
return new Type[0];
}
/**
* Defines the return type of the function.
*/
public abstract Type getResultType();
/**
* If the function depends on the context item or the default XPath namespace, then
* If the function depends on the context item, then
* this method should return {@code true}.
*
* <p>Note: Only if this is true, the contextNode parameter will be present in the
* {@link FunctionCall#call(Node, Object[])} method.
*/
public boolean dependsOnContext() {
return false;
}
/**
* Create a call on this function. This method is called, when a function call
* is found in the XPath expression.
*/
public abstract FunctionCall makeCallExpression();
/**
* Supported types of a custom XPath function. These can be used as {@link #getResultType() result types}
* or {@link #getArgumentTypes() argument types}.
*/
public enum Type {
/** Represents {@link String}. */
SINGLE_STRING,
/** Represents {@link Boolean}. */
SINGLE_BOOLEAN,
/** Represents {@link Integer}. */
SINGLE_INTEGER,
/** Represents any node. Usually used as an argument type. */
SINGLE_ELEMENT,
/** Represents a {@link java.util.List} of {@link String}, potentially empty. */
STRING_SEQUENCE,
/** Represents a {@link java.util.Optional} {@link String}. */
OPTIONAL_STRING,
/** Represents a {@link java.util.Optional} {@link Double}. */
OPTIONAL_DECIMAL,
}
/**
* Provides the actual implementation of a custom XPath function.
*/
public interface FunctionCall {
/**
* This method is called at runtime to evaluate the XPath function expression.
*
* @param contextNode the context node or {@code null}, if this function doesn't depend on the context.
* See {@link XPathFunctionDefinition#dependsOnContext()}.
* @param arguments The arguments converted as the corresponding java types.
* See {@link XPathFunctionDefinition#getArgumentTypes()}.
* @return The result of the function. This should be the corresponding java type of
* {@link XPathFunctionDefinition#getResultType()}.
* @throws XPathFunctionException when any problem during evaluation occurs, like invalid arguments.
*/
Object call(@Nullable Node contextNode, Object[] arguments) throws XPathFunctionException;
/**
* This is called once before the function is evaluated. It can be used to optimize the
* implementation by doing expensive operations only once and cache the result.
* This is useful, if the argument of the function is of type {@link String} and is provided
* as a String literal in the XPath expression.
*
* <p>This is an optional step. The default implementation does nothing.
*
* @param arguments The arguments converted as the corresponding java types.
* See {@link XPathFunctionDefinition#getArgumentTypes()}.
* Note: This array might contain {@code null} elements, if the values are
* not known yet because they are dynamic. Only literal values are available.
* @throws XPathFunctionException when any problem during initialization occurs, like invalid arguments.
*/
default void staticInit(Object[] arguments) throws XPathFunctionException {
// default implementation does nothing
}
Object call(Node contextNode, Object[] arguments) throws XPathFunctionException;
}
}

View File

@ -4,6 +4,10 @@
package net.sourceforge.pmd.lang.rule.xpath.impl;
/**
* Indicates a problem during the execution of a custom
* XPath function.
*/
public class XPathFunctionException extends Exception {
public XPathFunctionException(String message) {
super(message);

View File

@ -0,0 +1,172 @@
/*
* BSD-style license; for more info see http://pmd.sourceforge.net/license.html
*/
package net.sourceforge.pmd.lang.rule.xpath.internal;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import javax.xml.namespace.QName;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.rule.xpath.impl.XPathFunctionDefinition;
import net.sourceforge.pmd.lang.rule.xpath.impl.XPathFunctionException;
import net.sf.saxon.expr.Expression;
import net.sf.saxon.expr.StaticContext;
import net.sf.saxon.expr.StringLiteral;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.lib.ExtensionFunctionCall;
import net.sf.saxon.lib.ExtensionFunctionDefinition;
import net.sf.saxon.om.EmptyAtomicSequence;
import net.sf.saxon.om.Sequence;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.pattern.NodeKindTest;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.value.BigDecimalValue;
import net.sf.saxon.value.BooleanValue;
import net.sf.saxon.value.EmptySequence;
import net.sf.saxon.value.Int64Value;
import net.sf.saxon.value.SequenceExtent;
import net.sf.saxon.value.SequenceType;
import net.sf.saxon.value.StringValue;
/**
* Converts PMD's {@link XPathFunctionDefinition} into Saxon's {@link ExtensionFunctionDefinition}.
*/
public class SaxonExtensionFunctionDefinitionAdapter extends ExtensionFunctionDefinition {
private static final SequenceType SINGLE_ELEMENT_SEQUENCE_TYPE = NodeKindTest.ELEMENT.one();
private final XPathFunctionDefinition definition;
public SaxonExtensionFunctionDefinitionAdapter(XPathFunctionDefinition definition) {
this.definition = definition;
}
private SequenceType convertToSequenceType(XPathFunctionDefinition.Type type) {
switch (type) {
case SINGLE_STRING: return SequenceType.SINGLE_STRING;
case SINGLE_BOOLEAN: return SequenceType.SINGLE_BOOLEAN;
case SINGLE_ELEMENT: return SINGLE_ELEMENT_SEQUENCE_TYPE;
case SINGLE_INTEGER: return SequenceType.SINGLE_INTEGER;
case STRING_SEQUENCE: return SequenceType.STRING_SEQUENCE;
case OPTIONAL_STRING: return SequenceType.OPTIONAL_STRING;
case OPTIONAL_DECIMAL: return SequenceType.OPTIONAL_DECIMAL;
default:
throw new UnsupportedOperationException("Type " + type + " is not supported");
}
}
private SequenceType[] convertToSequenceTypes(XPathFunctionDefinition.Type[] types) {
SequenceType[] result = new SequenceType[types.length];
for (int i = 0; i < types.length; i++) {
result[i] = convertToSequenceType(types[i]);
}
return result;
}
@Override
public StructuredQName getFunctionQName() {
QName qName = definition.getQName();
return new StructuredQName(qName.getPrefix(), qName.getNamespaceURI(), qName.getLocalPart());
}
@Override
public SequenceType[] getArgumentTypes() {
return convertToSequenceTypes(definition.getArgumentTypes());
}
@Override
public SequenceType getResultType(SequenceType[] suppliedArgumentTypes) {
return convertToSequenceType(definition.getResultType());
}
@Override
public boolean dependsOnFocus() {
return definition.dependsOnContext();
}
@Override
public ExtensionFunctionCall makeCallExpression() {
XPathFunctionDefinition.FunctionCall call = definition.makeCallExpression();
return new ExtensionFunctionCall() {
@Override
public Expression rewrite(StaticContext context, Expression[] arguments) throws XPathException {
Object[] convertedArguments = new Object[definition.getArgumentTypes().length];
for (int i = 0; i < convertedArguments.length; i++) {
if (arguments[i] instanceof StringLiteral) {
convertedArguments[i] = ((StringLiteral) arguments[i]).getStringValue();
}
}
try {
call.staticInit(convertedArguments);
} catch (XPathFunctionException e) {
XPathException xPathException = new XPathException(e);
xPathException.setIsStaticError(true);
throw xPathException;
}
return null;
}
@Override
public Sequence call(XPathContext context, Sequence[] arguments) throws XPathException {
Node contextNode = null;
if (definition.dependsOnContext()) {
contextNode = XPathElementToNodeHelper.itemToNode(context.getContextItem());
}
Object[] convertedArguments = new Object[definition.getArgumentTypes().length];
for (int i = 0; i < convertedArguments.length; i++) {
switch (definition.getArgumentTypes()[i]) {
case SINGLE_STRING:
convertedArguments[i] = arguments[i].head().getStringValue();
break;
case SINGLE_ELEMENT:
convertedArguments[i] = arguments[i].head();
break;
default:
throw new UnsupportedOperationException("Don't know how to convert argument type " + definition.getArgumentTypes()[i]);
}
}
Object result = null;
try {
result = call.call(contextNode, convertedArguments);
} catch (XPathFunctionException e) {
throw new XPathException(e);
}
Sequence convertedResult = null;
switch (definition.getResultType()) {
case SINGLE_BOOLEAN:
convertedResult = BooleanValue.get((Boolean) result);
break;
case SINGLE_INTEGER:
convertedResult = Int64Value.makeIntegerValue((Integer) result);
break;
case SINGLE_STRING:
convertedResult = new StringValue((String) result);
break;
case OPTIONAL_STRING:
convertedResult = result instanceof Optional && ((Optional<String>) result).isPresent()
? new StringValue(((Optional<String>) result).get())
: EmptyAtomicSequence.INSTANCE;
break;
case STRING_SEQUENCE:
convertedResult = result instanceof List
? new SequenceExtent(((List<String>) result).stream().map(StringValue::new).collect(Collectors.toList()))
: EmptySequence.getInstance();
break;
case OPTIONAL_DECIMAL:
convertedResult = result instanceof Optional && ((Optional<Double>) result).isPresent()
? new BigDecimalValue(((Optional<Double>) result).get())
: EmptySequence.getInstance();
break;
default:
throw new UnsupportedOperationException("Don't know how to convert result type " + definition.getResultType());
}
return convertedResult;
}
};
}
}

View File

@ -5,14 +5,11 @@
package net.sourceforge.pmd.lang.rule.xpath.internal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javax.xml.namespace.QName;
import org.apache.commons.lang3.exception.ContextedRuntimeException;
import org.slf4j.Logger;
@ -24,7 +21,6 @@ import net.sourceforge.pmd.lang.rule.xpath.PmdXPathException;
import net.sourceforge.pmd.lang.rule.xpath.PmdXPathException.Phase;
import net.sourceforge.pmd.lang.rule.xpath.XPathVersion;
import net.sourceforge.pmd.lang.rule.xpath.impl.XPathFunctionDefinition;
import net.sourceforge.pmd.lang.rule.xpath.impl.XPathFunctionException;
import net.sourceforge.pmd.lang.rule.xpath.impl.XPathHandler;
import net.sourceforge.pmd.properties.PropertyDescriptor;
import net.sourceforge.pmd.util.DataMap;
@ -33,33 +29,19 @@ import net.sourceforge.pmd.util.DataMap.SimpleDataKey;
import net.sf.saxon.Configuration;
import net.sf.saxon.expr.Expression;
import net.sf.saxon.expr.LocalVariableReference;
import net.sf.saxon.expr.StaticContext;
import net.sf.saxon.expr.StringLiteral;
import net.sf.saxon.expr.XPathContext;
import net.sf.saxon.lib.ExtensionFunctionCall;
import net.sf.saxon.lib.ExtensionFunctionDefinition;
import net.sf.saxon.lib.NamespaceConstant;
import net.sf.saxon.om.AtomicSequence;
import net.sf.saxon.om.EmptyAtomicSequence;
import net.sf.saxon.om.Item;
import net.sf.saxon.om.NamePool;
import net.sf.saxon.om.Sequence;
import net.sf.saxon.om.SequenceIterator;
import net.sf.saxon.om.StructuredQName;
import net.sf.saxon.pattern.NodeKindTest;
import net.sf.saxon.sxpath.IndependentContext;
import net.sf.saxon.sxpath.XPathDynamicContext;
import net.sf.saxon.sxpath.XPathEvaluator;
import net.sf.saxon.sxpath.XPathExpression;
import net.sf.saxon.sxpath.XPathVariable;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.value.BigDecimalValue;
import net.sf.saxon.value.BooleanValue;
import net.sf.saxon.value.EmptySequence;
import net.sf.saxon.value.Int64Value;
import net.sf.saxon.value.SequenceExtent;
import net.sf.saxon.value.SequenceType;
import net.sf.saxon.value.StringValue;
/**
@ -218,7 +200,7 @@ public class SaxonXPathRuleQuery {
}
for (XPathFunctionDefinition xpathFun : xPathHandler.getRegisteredExtensionFunctions()) {
ExtensionFunctionDefinition fun = convertAbstractXPathFunctionDefinition(xpathFun);
ExtensionFunctionDefinition fun = new SaxonExtensionFunctionDefinitionAdapter(xpathFun);
StructuredQName qname = fun.getFunctionQName();
staticCtx.declareNamespace(qname.getPrefix(), qname.getURI());
this.configuration.registerExtensionFunction(fun);
@ -303,132 +285,4 @@ public class SaxonXPathRuleQuery {
return local;
}
}
public static ExtensionFunctionDefinition convertAbstractXPathFunctionDefinition(XPathFunctionDefinition definition) {
final SequenceType SINGLE_ELEMENT_SEQUENCE_TYPE = NodeKindTest.ELEMENT.one();
return new ExtensionFunctionDefinition() {
private SequenceType convertToSequenceType(XPathFunctionDefinition.Type type) {
switch (type) {
case SINGLE_STRING: return SequenceType.SINGLE_STRING;
case SINGLE_BOOLEAN: return SequenceType.SINGLE_BOOLEAN;
case SINGLE_ELEMENT: return SINGLE_ELEMENT_SEQUENCE_TYPE;
case SINGLE_INTEGER: return SequenceType.SINGLE_INTEGER;
case STRING_SEQUENCE: return SequenceType.STRING_SEQUENCE;
case OPTIONAL_STRING: return SequenceType.OPTIONAL_STRING;
case OPTIONAL_DECIMAL: return SequenceType.OPTIONAL_DECIMAL;
default:
throw new UnsupportedOperationException("Type " + type + " is not supported");
}
}
private SequenceType[] convertToSequenceTypes(XPathFunctionDefinition.Type[] types) {
SequenceType[] result = new SequenceType[types.length];
for (int i = 0; i < types.length; i++) {
result[i] = convertToSequenceType(types[i]);
}
return result;
}
@Override
public StructuredQName getFunctionQName() {
QName qName = definition.getQName();
return new StructuredQName(qName.getPrefix(), qName.getNamespaceURI(), qName.getLocalPart());
}
@Override
public SequenceType[] getArgumentTypes() {
return convertToSequenceTypes(definition.getArgumentTypes());
}
@Override
public SequenceType getResultType(SequenceType[] suppliedArgumentTypes) {
return convertToSequenceType(definition.getResultType());
}
@Override
public boolean dependsOnFocus() {
return definition.dependsOnContext();
}
@Override
public ExtensionFunctionCall makeCallExpression() {
XPathFunctionDefinition.FunctionCall call = definition.makeCallExpression();
return new ExtensionFunctionCall() {
@Override
public Expression rewrite(StaticContext context, Expression[] arguments) throws XPathException {
Object[] convertedArguments = new Object[definition.getArgumentTypes().length];
for (int i = 0; i < convertedArguments.length; i++) {
if (arguments[i] instanceof StringLiteral) {
convertedArguments[i] = ((StringLiteral) arguments[i]).getStringValue();
}
}
try {
call.staticInit(convertedArguments);
} catch (XPathFunctionException e) {
XPathException xPathException = new XPathException(e);
xPathException.setIsStaticError(true);
throw xPathException;
}
return null;
}
@Override
public Sequence call(XPathContext context, Sequence[] arguments) throws XPathException {
Node contextNode = null;
if (definition.dependsOnContext()) {
contextNode = XPathElementToNodeHelper.itemToNode(context.getContextItem());
}
Object[] convertedArguments = new Object[definition.getArgumentTypes().length];
for (int i = 0; i < convertedArguments.length; i++) {
switch (definition.getArgumentTypes()[i]) {
case SINGLE_STRING:
convertedArguments[i] = arguments[i].head().getStringValue();
break;
case SINGLE_ELEMENT:
convertedArguments[i] = arguments[i].head();
break;
default:
throw new UnsupportedOperationException("Don't know how to convert argument type " + definition.getArgumentTypes()[i]);
}
}
Object result = null;
try {
result = call.call(contextNode, convertedArguments);
} catch (XPathFunctionException e) {
throw new XPathException(e);
}
Sequence convertedResult = null;
switch (definition.getResultType()) {
case SINGLE_BOOLEAN:
convertedResult = BooleanValue.get((Boolean) result);
break;
case SINGLE_INTEGER:
convertedResult = Int64Value.makeIntegerValue((Integer) result);
break;
case SINGLE_STRING:
convertedResult = new StringValue((String) result);
break;
case OPTIONAL_STRING:
convertedResult = result != null ? new StringValue((String) result) : EmptyAtomicSequence.INSTANCE;
break;
case STRING_SEQUENCE:
convertedResult = result != null ? new SequenceExtent(Arrays.stream((String[]) result).map(StringValue::new).collect(Collectors.toList()))
: EmptySequence.getInstance();
break;
case OPTIONAL_DECIMAL:
convertedResult = result != null && Double.isFinite((Double) result) ? new BigDecimalValue((Double) result)
: EmptySequence.getInstance();
break;
default:
throw new UnsupportedOperationException("Don't know how to convert result type " + definition.getResultType());
}
return convertedResult;
}
};
}
};
}
}

View File

@ -5,6 +5,7 @@
package net.sourceforge.pmd.lang.java.rule.xpath.internal;
import java.util.List;
import java.util.Optional;
import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
import net.sourceforge.pmd.lang.java.ast.JavaComment;
@ -47,10 +48,10 @@ public class GetCommentOnFunction extends BaseJavaXPathFunction {
List<JavaComment> commentList = contextNode.getFirstParentOfType(ASTCompilationUnit.class).getComments();
for (JavaComment comment : commentList) {
if (comment.getBeginLine() == codeBeginLine || comment.getEndLine() == codeEndLine) {
return comment.getText().toString();
return Optional.of(comment.getText().toString());
}
}
return null;
return Optional.<String>empty();
};
}
}

View File

@ -4,6 +4,7 @@
package net.sourceforge.pmd.lang.java.rule.xpath.internal;
import java.util.Collections;
import java.util.Set;
import net.sourceforge.pmd.lang.java.ast.ASTModifierList;
@ -43,9 +44,9 @@ public final class GetModifiersFun extends BaseJavaXPathFunction {
ASTModifierList modList = ((AccessNode) contextNode).getModifiers();
Set<JModifier> mods = explicit ? modList.getExplicitModifiers()
: modList.getEffectiveModifiers();
return CollectionUtil.map(mods, JModifier::getToken).toArray(new String[0]);
return CollectionUtil.map(mods, JModifier::getToken);
}
return null;
return Collections.<String>emptyList();
};
}
}

View File

@ -4,6 +4,8 @@
package net.sourceforge.pmd.lang.java.rule.xpath.internal;
import java.util.Optional;
import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.metrics.LanguageMetricsProvider;
import net.sourceforge.pmd.lang.metrics.Metric;
@ -61,7 +63,7 @@ public final class MetricFunction extends BaseJavaXPathFunction {
}
private static double getMetric(Node n, String metricKeyName) throws XPathFunctionException {
private static Optional<Double> getMetric(Node n, String metricKeyName) throws XPathFunctionException {
LanguageMetricsProvider provider =
n.getAstInfo().getLanguageProcessor().services().getLanguageMetricsProvider();
Metric<?, ?> metric = provider.getMetricWithName(metricKeyName);
@ -70,7 +72,7 @@ public final class MetricFunction extends BaseJavaXPathFunction {
}
Number computed = Metric.compute(metric, n, MetricOptions.emptyOptions());
return computed == null ? Double.NaN : computed.doubleValue();
return computed == null ? Optional.empty() : Optional.of(computed.doubleValue());
}
}

View File

@ -4,8 +4,6 @@
package net.sourceforge.pmd.lang.xml.rule;
import static net.sourceforge.pmd.lang.rule.xpath.internal.SaxonXPathRuleQuery.convertAbstractXPathFunctionDefinition;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -21,6 +19,7 @@ import net.sourceforge.pmd.lang.ast.Node;
import net.sourceforge.pmd.lang.rule.xpath.impl.XPathFunctionDefinition;
import net.sourceforge.pmd.lang.rule.xpath.impl.XPathHandler;
import net.sourceforge.pmd.lang.rule.xpath.internal.DomainConversion;
import net.sourceforge.pmd.lang.rule.xpath.internal.SaxonExtensionFunctionDefinitionAdapter;
import net.sourceforge.pmd.lang.xml.ast.XmlNode;
import net.sourceforge.pmd.lang.xml.ast.internal.XmlParserImpl.RootXmlNode;
import net.sourceforge.pmd.properties.PropertyDescriptor;
@ -82,7 +81,7 @@ final class SaxonDomXPathQuery {
for (XPathFunctionDefinition xpathFun : xpathHandler.getRegisteredExtensionFunctions()) {
ExtensionFunctionDefinition fun = convertAbstractXPathFunctionDefinition(xpathFun);
ExtensionFunctionDefinition fun = new SaxonExtensionFunctionDefinitionAdapter(xpathFun);
StructuredQName qname = fun.getFunctionQName();
xpathStaticContext.declareNamespace(qname.getPrefix(), qname.getURI());
this.configuration.registerExtensionFunction(fun);