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