001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2022 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.regex.Pattern; 025import java.util.stream.Collectors; 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 pattern for file names. */ 050 private final String filePattern; 051 052 /** The regexp to match check names against. */ 053 private final Pattern checkRegexp; 054 055 /** The pattern for check class names. */ 056 private final String checkPattern; 057 058 /** The regexp to match message names against. */ 059 private final Pattern messageRegexp; 060 061 /** The pattern for message names. */ 062 private final String messagePattern; 063 064 /** Module id filter. */ 065 private final String moduleId; 066 067 /** Xpath expression. */ 068 private final XPathExpression xpathExpression; 069 070 /** Xpath query. */ 071 private final String xpathQuery; 072 073 /** 074 * Creates a {@code XpathElement} instance. 075 * 076 * @param files regular expression for names of filtered files 077 * @param checks regular expression for filtered check classes 078 * @param message regular expression for messages. 079 * @param moduleId the module id 080 * @param query the xpath query 081 * @throws IllegalArgumentException if the xpath query is not expected. 082 */ 083 public XpathFilterElement(String files, String checks, 084 String message, String moduleId, String query) { 085 filePattern = files; 086 if (files == null) { 087 fileRegexp = null; 088 } 089 else { 090 fileRegexp = Pattern.compile(files); 091 } 092 checkPattern = checks; 093 if (checks == null) { 094 checkRegexp = null; 095 } 096 else { 097 checkRegexp = CommonUtil.createPattern(checks); 098 } 099 messagePattern = message; 100 if (message == null) { 101 messageRegexp = null; 102 } 103 else { 104 messageRegexp = Pattern.compile(message); 105 } 106 this.moduleId = moduleId; 107 xpathQuery = query; 108 if (xpathQuery == null) { 109 xpathExpression = null; 110 } 111 else { 112 final XPathEvaluator xpathEvaluator = new XPathEvaluator( 113 Configuration.newConfiguration()); 114 try { 115 xpathExpression = xpathEvaluator.createExpression(xpathQuery); 116 } 117 catch (XPathException ex) { 118 throw new IllegalArgumentException("Unexpected xpath query: " + xpathQuery, ex); 119 } 120 } 121 } 122 123 /** 124 * Creates a {@code XpathElement} instance. 125 * 126 * @param files regular expression for names of filtered files 127 * @param checks regular expression for filtered check classes 128 * @param message regular expression for messages. 129 * @param moduleId the module id 130 * @param query the xpath query 131 * @throws IllegalArgumentException if the xpath query is not correct. 132 */ 133 public XpathFilterElement(Pattern files, Pattern checks, Pattern message, 134 String moduleId, String query) { 135 if (files == null) { 136 filePattern = null; 137 fileRegexp = null; 138 } 139 else { 140 filePattern = files.pattern(); 141 fileRegexp = files; 142 } 143 if (checks == null) { 144 checkPattern = null; 145 checkRegexp = null; 146 } 147 else { 148 checkPattern = checks.pattern(); 149 checkRegexp = checks; 150 } 151 if (message == null) { 152 messagePattern = null; 153 messageRegexp = null; 154 } 155 else { 156 messagePattern = message.pattern(); 157 messageRegexp = message; 158 } 159 this.moduleId = moduleId; 160 xpathQuery = query; 161 if (xpathQuery == null) { 162 xpathExpression = null; 163 } 164 else { 165 final XPathEvaluator xpathEvaluator = new XPathEvaluator( 166 Configuration.newConfiguration()); 167 try { 168 xpathExpression = xpathEvaluator.createExpression(xpathQuery); 169 } 170 catch (XPathException ex) { 171 throw new IllegalArgumentException("Incorrect xpath query: " + xpathQuery, ex); 172 } 173 } 174 } 175 176 @Override 177 public boolean accept(TreeWalkerAuditEvent event) { 178 return !isFileNameAndModuleAndModuleNameMatching(event) 179 || !isMessageNameMatching(event) 180 || !isXpathQueryMatching(event); 181 } 182 183 /** 184 * Is matching by file name, module id and Check name. 185 * 186 * @param event event 187 * @return true if it is matching 188 */ 189 private boolean isFileNameAndModuleAndModuleNameMatching(TreeWalkerAuditEvent event) { 190 return event.getFileName() != null 191 && (fileRegexp == null || fileRegexp.matcher(event.getFileName()).find()) 192 && event.getViolation() != null 193 && (moduleId == null || moduleId.equals(event.getModuleId())) 194 && (checkRegexp == null || checkRegexp.matcher(event.getSourceName()).find()); 195 } 196 197 /** 198 * Is matching by message. 199 * 200 * @param event event 201 * @return true if it is matching or not set. 202 */ 203 private boolean isMessageNameMatching(TreeWalkerAuditEvent event) { 204 return messageRegexp == null || messageRegexp.matcher(event.getMessage()).find(); 205 } 206 207 /** 208 * Is matching by xpath query. 209 * 210 * @param event event 211 * @return true if it is matching or not set. 212 */ 213 private boolean isXpathQueryMatching(TreeWalkerAuditEvent event) { 214 boolean isMatching; 215 if (xpathExpression == null) { 216 isMatching = true; 217 } 218 else { 219 isMatching = false; 220 final List<AbstractNode> nodes = getItems(event) 221 .stream().map(AbstractNode.class::cast).collect(Collectors.toList()); 222 for (AbstractNode abstractNode : nodes) { 223 isMatching = abstractNode.getTokenType() == event.getTokenType() 224 && abstractNode.getLineNumber() == event.getLine() 225 && abstractNode.getColumnNumber() == event.getColumnCharIndex(); 226 if (isMatching) { 227 break; 228 } 229 } 230 } 231 return isMatching; 232 } 233 234 /** 235 * Returns list of nodes matching xpath expression given event. 236 * 237 * @param event {@code TreeWalkerAuditEvent} object 238 * @return list of nodes matching xpath expression given event 239 * @throws IllegalStateException if the xpath query could not be evaluated. 240 */ 241 private List<Item> getItems(TreeWalkerAuditEvent event) { 242 final RootNode rootNode; 243 if (event.getRootAst() == null) { 244 rootNode = null; 245 } 246 else { 247 rootNode = new RootNode(event.getRootAst()); 248 } 249 final List<Item> items; 250 try { 251 final XPathDynamicContext xpathDynamicContext = 252 xpathExpression.createDynamicContext(rootNode); 253 items = xpathExpression.evaluate(xpathDynamicContext); 254 } 255 catch (XPathException ex) { 256 throw new IllegalStateException("Cannot initialize context and evaluate query: " 257 + xpathQuery, ex); 258 } 259 return items; 260 } 261 262 @Override 263 public int hashCode() { 264 return Objects.hash(filePattern, checkPattern, messagePattern, moduleId, xpathQuery); 265 } 266 267 @Override 268 public boolean equals(Object other) { 269 if (this == other) { 270 return true; 271 } 272 if (other == null || getClass() != other.getClass()) { 273 return false; 274 } 275 final XpathFilterElement xpathFilter = (XpathFilterElement) other; 276 return Objects.equals(filePattern, xpathFilter.filePattern) 277 && Objects.equals(checkPattern, xpathFilter.checkPattern) 278 && Objects.equals(messagePattern, xpathFilter.messagePattern) 279 && Objects.equals(moduleId, xpathFilter.moduleId) 280 && Objects.equals(xpathQuery, xpathFilter.xpathQuery); 281 } 282 283}