001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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 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.getFileName() != null 139 && (fileRegexp == null || fileRegexp.matcher(event.getFileName()).find()) 140 && event.getViolation() != 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<AbstractNode> nodes = getItems(event) 169 .stream().map(AbstractNode.class::cast) 170 .toList(); 171 for (AbstractNode abstractNode : nodes) { 172 isMatching = abstractNode.getTokenType() == event.getTokenType() 173 && abstractNode.getLineNumber() == event.getLine() 174 && abstractNode.getColumnNumber() == event.getColumnCharIndex(); 175 if (isMatching) { 176 break; 177 } 178 } 179 } 180 return isMatching; 181 } 182 183 /** 184 * Returns list of nodes matching xpath expression given event. 185 * 186 * @param event {@code TreeWalkerAuditEvent} object 187 * @return list of nodes matching xpath expression given event 188 * @throws IllegalStateException if the xpath query could not be evaluated. 189 */ 190 private List<Item> getItems(TreeWalkerAuditEvent event) { 191 final RootNode rootNode; 192 if (event.getRootAst() == null) { 193 rootNode = null; 194 } 195 else { 196 rootNode = new RootNode(event.getRootAst()); 197 } 198 final List<Item> items; 199 try { 200 final XPathDynamicContext xpathDynamicContext = 201 xpathExpression.createDynamicContext(rootNode); 202 items = xpathExpression.evaluate(xpathDynamicContext); 203 } 204 catch (XPathException exc) { 205 throw new IllegalStateException("Cannot initialize context and evaluate query: " 206 + xpathQuery, exc); 207 } 208 return items; 209 } 210 211 @Override 212 public int hashCode() { 213 return Objects.hash(getPatternSafely(fileRegexp), getPatternSafely(checkRegexp), 214 getPatternSafely(messageRegexp), moduleId, xpathQuery); 215 } 216 217 @Override 218 public boolean equals(Object other) { 219 if (this == other) { 220 return true; 221 } 222 if (other == null || getClass() != other.getClass()) { 223 return false; 224 } 225 final XpathFilterElement xpathFilter = (XpathFilterElement) other; 226 return Objects.equals(getPatternSafely(fileRegexp), 227 getPatternSafely(xpathFilter.fileRegexp)) 228 && Objects.equals(getPatternSafely(checkRegexp), 229 getPatternSafely(xpathFilter.checkRegexp)) 230 && Objects.equals(getPatternSafely(messageRegexp), 231 getPatternSafely(xpathFilter.messageRegexp)) 232 && Objects.equals(moduleId, xpathFilter.moduleId) 233 && Objects.equals(xpathQuery, xpathFilter.xpathQuery); 234 } 235 236 /** 237 * Util method to get pattern String value from Pattern object safely, return null if 238 * pattern object is null. 239 * 240 * @param pattern pattern object 241 * @return value of pattern or null 242 */ 243 private static String getPatternSafely(Pattern pattern) { 244 String result = null; 245 if (pattern != null) { 246 result = pattern.pattern(); 247 } 248 return result; 249 } 250}