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 }