• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pmd / pmd / 43

20 Jun 2025 06:39PM UTC coverage: 78.375% (-0.002%) from 78.377%
43

push

github

adangel
Fix #1639 #5832: Use filtered comment text for UnnecessaryImport (#5833)

Merged pull request #5833 from adangel:java/issue-5832-unnecessaryimport

17714 of 23438 branches covered (75.58%)

Branch coverage included in aggregate %.

3 of 3 new or added lines in 1 file covered. (100.0%)

109 existing lines in 17 files now uncovered.

38908 of 48807 relevant lines covered (79.72%)

0.81 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.33
/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/internal/SaxonXPathRuleQuery.java
1
/*
2
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3
 */
4

5
package net.sourceforge.pmd.lang.rule.xpath.internal;
6

7
import java.util.ArrayList;
8
import java.util.HashMap;
9
import java.util.LinkedHashSet;
10
import java.util.List;
11
import java.util.Map;
12
import java.util.Set;
13

14
import org.apache.commons.lang3.exception.ContextedRuntimeException;
15
import org.slf4j.Logger;
16
import org.slf4j.LoggerFactory;
17

18
import net.sourceforge.pmd.lang.ast.Node;
19
import net.sourceforge.pmd.lang.ast.RootNode;
20
import net.sourceforge.pmd.lang.rule.xpath.PmdXPathException;
21
import net.sourceforge.pmd.lang.rule.xpath.PmdXPathException.Phase;
22
import net.sourceforge.pmd.lang.rule.xpath.XPathVersion;
23
import net.sourceforge.pmd.lang.rule.xpath.impl.XPathFunctionDefinition;
24
import net.sourceforge.pmd.lang.rule.xpath.impl.XPathHandler;
25
import net.sourceforge.pmd.properties.PropertyDescriptor;
26
import net.sourceforge.pmd.util.DataMap;
27
import net.sourceforge.pmd.util.DataMap.SimpleDataKey;
28

29
import net.sf.saxon.Configuration;
30
import net.sf.saxon.expr.Expression;
31
import net.sf.saxon.expr.LocalVariableReference;
32
import net.sf.saxon.lib.ExtensionFunctionDefinition;
33
import net.sf.saxon.om.AtomicSequence;
34
import net.sf.saxon.om.Item;
35
import net.sf.saxon.om.NamePool;
36
import net.sf.saxon.om.NamespaceUri;
37
import net.sf.saxon.om.SequenceIterator;
38
import net.sf.saxon.om.StructuredQName;
39
import net.sf.saxon.sxpath.IndependentContext;
40
import net.sf.saxon.sxpath.XPathDynamicContext;
41
import net.sf.saxon.sxpath.XPathEvaluator;
42
import net.sf.saxon.sxpath.XPathExpression;
43
import net.sf.saxon.sxpath.XPathVariable;
44
import net.sf.saxon.trans.UncheckedXPathException;
45
import net.sf.saxon.trans.XPathException;
46

47

48
/**
49
 * This is a Saxon based XPathRule query.
50
 */
51
public class SaxonXPathRuleQuery {
52

53
    /**
54
     * Special nodeName that references the root expression.
55
     */
56
    static final String AST_ROOT = "_AST_ROOT_";
57

58
    private static final Logger LOG = LoggerFactory.getLogger(SaxonXPathRuleQuery.class);
1✔
59

60
    private static final NamePool NAME_POOL = new NamePool();
1✔
61

62
    /** Cache key for the wrapped tree for saxon. */
63
    private static final SimpleDataKey<AstTreeInfo> SAXON_TREE_CACHE_KEY = DataMap.simpleDataKey("saxon.tree");
1✔
64

65
    private final String xpathExpr;
66
    private final XPathVersion version;
67
    private final Map<PropertyDescriptor<?>, Object> properties;
68
    private final XPathHandler xPathHandler;
69
    private final List<String> rulechainQueries = new ArrayList<>();
1✔
70
    private Configuration configuration;
71

72
    /**
73
     * Contains for each nodeName a sub expression, used for implementing rule chain.
74
     */
75
    Map<String, List<Expression>> nodeNameToXPaths = new HashMap<>();
1✔
76

77
    /**
78
     * Representation of an XPath query, created at {@link #initialize()} using {@link #xpathExpr}.
79
     */
80
    XPathExpression xpathExpression;
81

82
    private final DeprecatedAttrLogger attrCtx;
83

84

85
    public SaxonXPathRuleQuery(String xpathExpr,
86
                               XPathVersion version,
87
                               Map<PropertyDescriptor<?>, Object> properties,
88
                               XPathHandler xPathHandler,
89
                               DeprecatedAttrLogger logger) throws PmdXPathException {
1✔
90
        this.xpathExpr = xpathExpr;
1✔
91
        this.version = version;
1✔
92
        this.properties = properties;
1✔
93
        this.xPathHandler = xPathHandler;
1✔
94
        this.attrCtx = logger;
1✔
95
        try {
96
            initialize();
1✔
UNCOV
97
        } catch (XPathException e) {
×
98
            throw wrapException(e, Phase.INITIALIZATION);
×
99
        }
1✔
100
    }
1✔
101

102

103
    public String getXpathExpression() {
UNCOV
104
        return xpathExpr;
×
105
    }
106

107

108
    public List<String> getRuleChainVisits() {
109
        return rulechainQueries;
1✔
110
    }
111

112

113
    public List<Node> evaluate(final Node node) {
114
        final AstTreeInfo documentNode = getDocumentNodeForRootNode(node);
1✔
115
        documentNode.setAttrCtx(attrCtx);
1✔
116
        try {
117

118
            // Map AST Node -> Saxon Node
119
            final XPathDynamicContext xpathDynamicContext = xpathExpression.createDynamicContext(documentNode.findWrapperFor(node));
1✔
120

121
            // XPath 2.0 sequences may contain duplicates
122
            final Set<Node> results = new LinkedHashSet<>();
1✔
123
            List<Expression> expressions = getExpressionsForLocalNameOrDefault(node.getXPathNodeName());
1✔
124
            for (Expression expression : expressions) {
1✔
125
                @SuppressWarnings("PMD.CloseResource")
126
                SequenceIterator iterator = expression.iterate(xpathDynamicContext.getXPathContextObject());
1✔
127
                Item current = iterator.next();
1✔
128
                while (current != null) {
1✔
129
                    if (current instanceof AstNodeOwner) {
1✔
130
                        results.add(((AstNodeOwner) current).getUnderlyingNode());
1✔
131
                    } else {
132
                        throw new XPathException("XPath rule expression returned a non-node (" + current.getClass() + "): " + current);
1✔
133
                    }
134
                    current = iterator.next();
1✔
135
                }
136
            }
1✔
137

138
            final List<Node> sortedRes = new ArrayList<>(results);
1✔
139
            sortedRes.sort(RuleChainAnalyzer.documentOrderComparator());
1✔
140
            return sortedRes;
1✔
141
        } catch (final XPathException e) {
1✔
142
            throw wrapException(e, Phase.EVALUATION);
1✔
UNCOV
143
        } catch (final UncheckedXPathException e) {
×
144
            throw wrapException(e.getXPathException(), Phase.EVALUATION);
×
145
        } finally {
146
            documentNode.setAttrCtx(DeprecatedAttrLogger.noop());
1✔
147
        }
148
    }
149

150
    private ContextedRuntimeException wrapException(XPathException e, Phase phase) {
151
        return new PmdXPathException(e, phase, xpathExpr, version);
1✔
152
    }
153

154
    // test only
155
    List<Expression> getExpressionsForLocalNameOrDefault(String nodeName) {
156
        List<Expression> expressions = nodeNameToXPaths.get(nodeName);
1✔
157
        if (expressions != null) {
1✔
158
            return expressions;
1✔
159
        }
160
        return nodeNameToXPaths.get(AST_ROOT);
1✔
161
    }
162

163
    // test only
164
    Expression getFallbackExpr() {
165
        return nodeNameToXPaths.get(SaxonXPathRuleQuery.AST_ROOT).get(0);
1✔
166
    }
167

168

169
    /**
170
     * Gets the DocumentNode representation for the whole AST in which the node is, that is, if the node is not the root
171
     * of the AST, then the AST is traversed all the way up until the root node is found. If the DocumentNode was
172
     * cached because this method was previously called, then a new DocumentNode will not be instanced.
173
     *
174
     * @param node the node from which the root node will be looked for.
175
     *
176
     * @return the DocumentNode representing the whole AST
177
     */
178
    private AstTreeInfo getDocumentNodeForRootNode(final Node node) {
179
        final RootNode root = node.getRoot();
1✔
180
        return root.getUserMap().computeIfAbsent(SAXON_TREE_CACHE_KEY, () -> new AstTreeInfo(root, configuration));
1✔
181
    }
182

183

184
    private void addExpressionForNode(String nodeName, Expression expression) {
185
        nodeNameToXPaths.computeIfAbsent(nodeName, n -> new ArrayList<>(2)).add(expression);
1✔
186
    }
1✔
187

188
    private void initialize() throws XPathException {
189
        this.configuration = Configuration.newConfiguration();
1✔
190
        this.configuration.setNamePool(getNamePool());
1✔
191

192
        StaticContextWithProperties staticCtx = new StaticContextWithProperties(this.configuration);
1✔
193
        staticCtx.setXPathLanguageLevel(version == XPathVersion.XPATH_3_1 ? 31 : 20);
1!
194
        staticCtx.declareNamespace("fn", NamespaceUri.FN);
1✔
195

196
        for (final PropertyDescriptor<?> propertyDescriptor : properties.keySet()) {
1✔
197
            final String name = propertyDescriptor.name();
1✔
198
            if (!"xpath".equals(name)) {
1✔
199
                staticCtx.declareProperty(propertyDescriptor);
1✔
200
            }
201
        }
1✔
202

203
        for (XPathFunctionDefinition xpathFun : xPathHandler.getRegisteredExtensionFunctions()) {
1✔
204
            ExtensionFunctionDefinition fun = new SaxonExtensionFunctionDefinitionAdapter(xpathFun);
1✔
205
            StructuredQName qname = fun.getFunctionQName();
1✔
206
            staticCtx.declareNamespace(qname.getPrefix(), qname.getNamespaceUri());
1✔
207
            this.configuration.registerExtensionFunction(fun);
1✔
208
        }
1✔
209

210
        final XPathEvaluator xpathEvaluator = new XPathEvaluator(configuration);
1✔
211
        xpathEvaluator.setStaticContext(staticCtx);
1✔
212

213
        xpathExpression = xpathEvaluator.createExpression(xpathExpr);
1✔
214
        analyzeXPathForRuleChain(xpathEvaluator);
1✔
215

216
    }
1✔
217

218
    private void analyzeXPathForRuleChain(final XPathEvaluator xpathEvaluator) {
219
        final Expression expr = xpathExpression.getInternalExpression();
1✔
220

221
        boolean useRuleChain = true;
1✔
222

223
        // First step: Split the union venn expressions into single expressions
224
        Iterable<Expression> subexpressions = SaxonExprTransformations.splitUnions(expr);
1✔
225

226
        // Second step: Analyze each expression separately
227
        for (final Expression subexpression : subexpressions) { // final because of checkstyle
1✔
228
            Expression modified = subexpression;
1✔
229
            modified = SaxonExprTransformations.hoistFilters(modified);
1✔
230
            modified = SaxonExprTransformations.reduceRoot(modified);
1✔
231
            modified = SaxonExprTransformations.copyTopLevelLets(modified, expr);
1✔
232
            RuleChainAnalyzer rca = new RuleChainAnalyzer(xpathEvaluator.getConfiguration());
1✔
233
            final Expression finalExpr = rca.visit(modified); // final because of lambda
1✔
234

235
            if (!rca.getRootElements().isEmpty()) {
1✔
236
                rca.getRootElements().forEach(it -> addExpressionForNode(it, finalExpr));
1✔
237
            } else {
238
                // couldn't find a root element for the expression, that means, we can't use rule chain at all
239
                // even though, it would be possible for part of the expression.
240
                useRuleChain = false;
1✔
241
                break;
1✔
242
            }
243
        }
1✔
244

245
        if (useRuleChain) {
1✔
246
            rulechainQueries.addAll(nodeNameToXPaths.keySet());
1✔
247
        } else {
248
            nodeNameToXPaths.clear();
1✔
249
            LOG.debug("Unable to use RuleChain for XPath: {}", xpathExpr);
1✔
250
        }
251

252
        // always add fallback expression
253
        addExpressionForNode(AST_ROOT, xpathExpression.getInternalExpression());
1✔
254
    }
1✔
255

256
    public static NamePool getNamePool() {
257
        return NAME_POOL;
1✔
258
    }
259

260
    final class StaticContextWithProperties extends IndependentContext {
261

262
        private final Map<StructuredQName, PropertyDescriptor<?>> propertiesByName = new HashMap<>();
1✔
263

264
        StaticContextWithProperties(Configuration config) {
1✔
265
            super(config);
1✔
266
            // This statement is necessary for Saxon to support sequence-valued attributes
267
            getPackageData().setSchemaAware(true);
1✔
268
        }
1✔
269

270
        public void declareProperty(PropertyDescriptor<?> prop) {
271
            XPathVariable var = declareVariable(null, prop.name());
1✔
272
            propertiesByName.put(var.getVariableQName(), prop);
1✔
273
        }
1✔
274

275
        @Override
276
        public Expression bindVariable(StructuredQName qName) throws XPathException {
277
            LocalVariableReference local = (LocalVariableReference) super.bindVariable(qName);
1✔
278
            PropertyDescriptor<?> prop = propertiesByName.get(qName);
1✔
279
            if (prop == null || prop.defaultValue() == null) {
1!
UNCOV
280
                return local;
×
281
            }
282

283
            // TODO Saxon optimizer bug (js/codestyle.xml/AssignmentInOperand)
284
            Object actualValue = properties.getOrDefault(prop, prop.defaultValue());
1✔
285
            AtomicSequence converted = DomainConversion.convert(actualValue);
1✔
286
            local.setStaticType(null, converted, 0);
1✔
287
            return local;
1✔
288
        }
289
    }
290
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc