View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2025 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.coding;
21  
22  import java.util.List;
23  import java.util.stream.Collectors;
24  
25  import com.puppycrawl.tools.checkstyle.StatelessCheck;
26  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27  import com.puppycrawl.tools.checkstyle.api.DetailAST;
28  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
29  import com.puppycrawl.tools.checkstyle.xpath.AbstractNode;
30  import com.puppycrawl.tools.checkstyle.xpath.RootNode;
31  import net.sf.saxon.Configuration;
32  import net.sf.saxon.om.Item;
33  import net.sf.saxon.sxpath.XPathDynamicContext;
34  import net.sf.saxon.sxpath.XPathEvaluator;
35  import net.sf.saxon.sxpath.XPathExpression;
36  import net.sf.saxon.trans.XPathException;
37  
38  /**
39   * <div>
40   * Evaluates Xpath query and report violation on all matching AST nodes. This check allows
41   * user to implement custom checks using Xpath. If Xpath query is not specified explicitly,
42   * then the check does nothing.
43   * </div>
44   *
45   * <p>
46   * It is recommended to define custom message for violation to explain what is not allowed and what
47   * to use instead, default message might be too abstract. To customize a message you need to
48   * add {@code message} element with <b>matchxpath.match</b> as {@code key} attribute and
49   * desired message as {@code value} attribute.
50   * </p>
51   *
52   * <p>
53   * Please read more about Xpath syntax at
54   * <a href="https://www.saxonica.com/html/documentation10/expressions/index.html">Xpath Syntax</a>.
55   * Information regarding Xpath functions can be found at
56   * <a href="https://www.saxonica.com/html/documentation10/functions/fn/index.html">
57   *     XSLT/XPath Reference</a>.
58   * Note, that <b>@text</b> attribute can be used only with token types that are listed in
59   * <a href="https://github.com/checkstyle/checkstyle/search?q=%22TOKEN_TYPES_WITH_TEXT_ATTRIBUTE+%3D+Arrays.asList%22">
60   *     XpathUtil</a>.
61   * </p>
62   * <ul>
63   * <li>
64   * Property {@code query} - Specify Xpath query.
65   * Type is {@code java.lang.String}.
66   * Default value is {@code ""}.
67   * </li>
68   * </ul>
69   *
70   * <p>
71   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
72   * </p>
73   *
74   * <p>
75   * Violation Message Keys:
76   * </p>
77   * <ul>
78   * <li>
79   * {@code matchxpath.match}
80   * </li>
81   * </ul>
82   *
83   * @since 8.39
84   */
85  @StatelessCheck
86  public class MatchXpathCheck extends AbstractCheck {
87  
88      /**
89       * A key is pointing to the warning message text provided by user.
90       */
91      public static final String MSG_KEY = "matchxpath.match";
92  
93      /** Specify Xpath query. */
94      private String query = "";
95  
96      /** Xpath expression. */
97      private XPathExpression xpathExpression;
98  
99      /**
100      * Setter to specify Xpath query.
101      *
102      * @param query Xpath query.
103      * @throws IllegalStateException if creation of xpath expression fails
104      * @since 8.39
105      */
106     public void setQuery(String query) {
107         this.query = query;
108         if (!query.isEmpty()) {
109             try {
110                 final XPathEvaluator xpathEvaluator =
111                         new XPathEvaluator(Configuration.newConfiguration());
112                 xpathExpression = xpathEvaluator.createExpression(query);
113             }
114             catch (XPathException ex) {
115                 throw new IllegalStateException("Creating Xpath expression failed: " + query, ex);
116             }
117         }
118     }
119 
120     @Override
121     public int[] getDefaultTokens() {
122         return getRequiredTokens();
123     }
124 
125     @Override
126     public int[] getAcceptableTokens() {
127         return getRequiredTokens();
128     }
129 
130     @Override
131     public int[] getRequiredTokens() {
132         return CommonUtil.EMPTY_INT_ARRAY;
133     }
134 
135     @Override
136     public boolean isCommentNodesRequired() {
137         return true;
138     }
139 
140     @Override
141     public void beginTree(DetailAST rootAST) {
142         if (!query.isEmpty()) {
143             final List<DetailAST> matchingNodes = findMatchingNodesByXpathQuery(rootAST);
144             matchingNodes.forEach(node -> log(node, MSG_KEY));
145         }
146     }
147 
148     /**
149      * Find nodes that match query.
150      *
151      * @param rootAST root node
152      * @return list of matching nodes
153      * @throws IllegalStateException if evaluation of xpath query fails
154      */
155     private List<DetailAST> findMatchingNodesByXpathQuery(DetailAST rootAST) {
156         try {
157             final RootNode rootNode = new RootNode(rootAST);
158             final XPathDynamicContext xpathDynamicContext =
159                     xpathExpression.createDynamicContext(rootNode);
160             final List<Item> matchingItems = xpathExpression.evaluate(xpathDynamicContext);
161             return matchingItems.stream()
162                     .map(item -> (DetailAST) ((AbstractNode) item).getUnderlyingNode())
163                     .collect(Collectors.toUnmodifiableList());
164         }
165         catch (XPathException ex) {
166             throw new IllegalStateException("Evaluation of Xpath query failed: " + query, ex);
167         }
168     }
169 }