001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.filters; 021 022import java.util.List; 023import java.util.Objects; 024import java.util.Optional; 025import java.util.regex.Pattern; 026 027import com.puppycrawl.tools.checkstyle.TreeWalkerAuditEvent; 028import com.puppycrawl.tools.checkstyle.TreeWalkerFilter; 029import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 030import com.puppycrawl.tools.checkstyle.xpath.AbstractNode; 031import com.puppycrawl.tools.checkstyle.xpath.RootNode; 032import net.sf.saxon.Configuration; 033import net.sf.saxon.om.Item; 034import net.sf.saxon.sxpath.XPathDynamicContext; 035import net.sf.saxon.sxpath.XPathEvaluator; 036import net.sf.saxon.sxpath.XPathExpression; 037import net.sf.saxon.trans.XPathException; 038 039/** 040 * This filter element is immutable and processes {@link TreeWalkerAuditEvent} 041 * objects based on the criteria of file, check, module id, xpathQuery. 042 * 043 */ 044public final class XpathFilterElement implements TreeWalkerFilter { 045 046 /** The regexp to match file names against. */ 047 private final Pattern fileRegexp; 048 049 /** The regexp to match check names against. */ 050 private final Pattern checkRegexp; 051 052 /** The regexp to match message names against. */ 053 private final Pattern messageRegexp; 054 055 /** Module id filter. */ 056 private final String moduleId; 057 058 /** Xpath expression. */ 059 private final XPathExpression xpathExpression; 060 061 /** Xpath query. */ 062 private final String xpathQuery; 063 064 /** Indicates if all properties are set to null. */ 065 private final boolean isEmptyConfig; 066 067 /** 068 * Creates a {@code XpathElement} instance. 069 * 070 * @param files regular expression for names of filtered files 071 * @param checks regular expression for filtered check classes 072 * @param message regular expression for messages. 073 * @param moduleId the module id 074 * @param query the xpath query 075 * @throws IllegalArgumentException if the xpath query is not expected. 076 */ 077 public XpathFilterElement(String files, String checks, 078 String message, String moduleId, String query) { 079 this(Optional.ofNullable(files).map(Pattern::compile).orElse(null), 080 Optional.ofNullable(checks).map(CommonUtil::createPattern).orElse(null), 081 Optional.ofNullable(message).map(Pattern::compile).orElse(null), 082 moduleId, 083 query); 084 } 085 086 /** 087 * Creates a {@code XpathElement} instance. 088 * 089 * @param files regular expression for names of filtered files 090 * @param checks regular expression for filtered check classes 091 * @param message regular expression for messages. 092 * @param moduleId the module id 093 * @param query the xpath query 094 * @throws IllegalArgumentException if the xpath query is not correct. 095 */ 096 public XpathFilterElement(Pattern files, Pattern checks, Pattern message, 097 String moduleId, String query) { 098 fileRegexp = files; 099 checkRegexp = checks; 100 messageRegexp = message; 101 this.moduleId = moduleId; 102 xpathQuery = query; 103 if (xpathQuery == null) { 104 xpathExpression = null; 105 } 106 else { 107 final XPathEvaluator xpathEvaluator = new XPathEvaluator( 108 Configuration.newConfiguration()); 109 try { 110 xpathExpression = xpathEvaluator.createExpression(xpathQuery); 111 } 112 catch (XPathException exc) { 113 throw new IllegalArgumentException("Incorrect xpath query: " + xpathQuery, exc); 114 } 115 } 116 isEmptyConfig = fileRegexp == null 117 && checkRegexp == null 118 && messageRegexp == null 119 && moduleId == null 120 && xpathExpression == null; 121 } 122 123 @Override 124 public boolean accept(TreeWalkerAuditEvent event) { 125 return isEmptyConfig 126 || !isFileNameAndModuleAndModuleNameMatching(event) 127 || !isMessageNameMatching(event) 128 || !isXpathQueryMatching(event); 129 } 130 131 /** 132 * Is matching by file name, module id and Check name. 133 * 134 * @param event event 135 * @return true if it is matching 136 */ 137 private boolean isFileNameAndModuleAndModuleNameMatching(TreeWalkerAuditEvent event) { 138 return event.fileName() != null 139 && (fileRegexp == null || fileRegexp.matcher(event.fileName()).find()) 140 && event.violation() != null 141 && (moduleId == null || moduleId.equals(event.getModuleId())) 142 && (checkRegexp == null || checkRegexp.matcher(event.getSourceName()).find()); 143 } 144 145 /** 146 * Is matching by message. 147 * 148 * @param event event 149 * @return true if it is matching or not set. 150 */ 151 private boolean isMessageNameMatching(TreeWalkerAuditEvent event) { 152 return messageRegexp == null || messageRegexp.matcher(event.getMessage()).find(); 153 } 154 155 /** 156 * Is matching by xpath query. 157 * 158 * @param event event 159 * @return true if it is matching or not set. 160 */ 161 private boolean isXpathQueryMatching(TreeWalkerAuditEvent event) { 162 boolean isMatching; 163 if (xpathExpression == null) { 164 isMatching = true; 165 } 166 else { 167 isMatching = false; 168 final List<Item> nodes = getItems(event) 169 .stream() 170 .toList(); 171 for (Item item : nodes) { 172 final AbstractNode abstractNode = (AbstractNode) item; 173 isMatching = abstractNode.getTokenType() == event.getTokenType() 174 && abstractNode.getLineNumber() == event.getLine() 175 && abstractNode.getColumnNumber() == event.getColumnCharIndex(); 176 if (isMatching) { 177 break; 178 } 179 } 180 } 181 return isMatching; 182 } 183 184 /** 185 * Returns list of nodes matching xpath expression given event. 186 * 187 * @param event {@code TreeWalkerAuditEvent} object 188 * @return list of nodes matching xpath expression given event 189 * @throws IllegalStateException if the xpath query could not be evaluated. 190 */ 191 private List<Item> getItems(TreeWalkerAuditEvent event) { 192 final RootNode rootNode; 193 if (event.rootAst() == null) { 194 rootNode = null; 195 } 196 else { 197 rootNode = new RootNode(event.rootAst()); 198 } 199 final List<Item> items; 200 try { 201 final XPathDynamicContext xpathDynamicContext = 202 xpathExpression.createDynamicContext(rootNode); 203 items = xpathExpression.evaluate(xpathDynamicContext); 204 } 205 catch (XPathException exc) { 206 throw new IllegalStateException("Cannot initialize context and evaluate query: " 207 + xpathQuery, exc); 208 } 209 return items; 210 } 211 212 @Override 213 public int hashCode() { 214 return Objects.hash(getPatternSafely(fileRegexp), getPatternSafely(checkRegexp), 215 getPatternSafely(messageRegexp), moduleId, xpathQuery); 216 } 217 218 @Override 219 public boolean equals(Object other) { 220 if (this == other) { 221 return true; 222 } 223 if (other == null || getClass() != other.getClass()) { 224 return false; 225 } 226 final XpathFilterElement xpathFilter = (XpathFilterElement) other; 227 return Objects.equals(getPatternSafely(fileRegexp), 228 getPatternSafely(xpathFilter.fileRegexp)) 229 && Objects.equals(getPatternSafely(checkRegexp), 230 getPatternSafely(xpathFilter.checkRegexp)) 231 && Objects.equals(getPatternSafely(messageRegexp), 232 getPatternSafely(xpathFilter.messageRegexp)) 233 && Objects.equals(moduleId, xpathFilter.moduleId) 234 && Objects.equals(xpathQuery, xpathFilter.xpathQuery); 235 } 236 237 /** 238 * Util method to get pattern String value from Pattern object safely, return null if 239 * pattern object is null. 240 * 241 * @param pattern pattern object 242 * @return value of pattern or null 243 */ 244 private static String getPatternSafely(Pattern pattern) { 245 String result = null; 246 if (pattern != null) { 247 result = pattern.pattern(); 248 } 249 return result; 250 } 251}