001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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 regexp to match check names against. */ 051 private final Pattern checkRegexp; 052 053 /** The regexp to match message names against. */ 054 private final Pattern messageRegexp; 055 056 /** Module id filter. */ 057 private final String moduleId; 058 059 /** Xpath expression. */ 060 private final XPathExpression xpathExpression; 061 062 /** Xpath query. */ 063 private final String xpathQuery; 064 065 /** Indicates if all properties are set to null. */ 066 private final boolean isEmptyConfig; 067 068 /** 069 * Creates a {@code XpathElement} instance. 070 * 071 * @param files regular expression for names of filtered files 072 * @param checks regular expression for filtered check classes 073 * @param message regular expression for messages. 074 * @param moduleId the module id 075 * @param query the xpath query 076 * @throws IllegalArgumentException if the xpath query is not expected. 077 */ 078 public XpathFilterElement(String files, String checks, 079 String message, String moduleId, String query) { 080 this(Optional.ofNullable(files).map(Pattern::compile).orElse(null), 081 Optional.ofNullable(checks).map(CommonUtil::createPattern).orElse(null), 082 Optional.ofNullable(message).map(Pattern::compile).orElse(null), 083 moduleId, 084 query); 085 } 086 087 /** 088 * Creates a {@code XpathElement} instance. 089 * 090 * @param files regular expression for names of filtered files 091 * @param checks regular expression for filtered check classes 092 * @param message regular expression for messages. 093 * @param moduleId the module id 094 * @param query the xpath query 095 * @throws IllegalArgumentException if the xpath query is not correct. 096 */ 097 public XpathFilterElement(Pattern files, Pattern checks, Pattern message, 098 String moduleId, String query) { 099 fileRegexp = files; 100 checkRegexp = checks; 101 messageRegexp = message; 102 this.moduleId = moduleId; 103 xpathQuery = query; 104 if (xpathQuery == null) { 105 xpathExpression = null; 106 } 107 else { 108 final XPathEvaluator xpathEvaluator = new XPathEvaluator( 109 Configuration.newConfiguration()); 110 try { 111 xpathExpression = xpathEvaluator.createExpression(xpathQuery); 112 } 113 catch (XPathException ex) { 114 throw new IllegalArgumentException("Incorrect xpath query: " + xpathQuery, ex); 115 } 116 } 117 isEmptyConfig = fileRegexp == null 118 && checkRegexp == null 119 && messageRegexp == null 120 && moduleId == null 121 && xpathExpression == null; 122 } 123 124 @Override 125 public boolean accept(TreeWalkerAuditEvent event) { 126 return isEmptyConfig 127 || !isFileNameAndModuleAndModuleNameMatching(event) 128 || !isMessageNameMatching(event) 129 || !isXpathQueryMatching(event); 130 } 131 132 /** 133 * Is matching by file name, module id and Check name. 134 * 135 * @param event event 136 * @return true if it is matching 137 */ 138 private boolean isFileNameAndModuleAndModuleNameMatching(TreeWalkerAuditEvent event) { 139 return event.getFileName() != null 140 && (fileRegexp == null || fileRegexp.matcher(event.getFileName()).find()) 141 && event.getViolation() != null 142 && (moduleId == null || moduleId.equals(event.getModuleId())) 143 && (checkRegexp == null || checkRegexp.matcher(event.getSourceName()).find()); 144 } 145 146 /** 147 * Is matching by message. 148 * 149 * @param event event 150 * @return true if it is matching or not set. 151 */ 152 private boolean isMessageNameMatching(TreeWalkerAuditEvent event) { 153 return messageRegexp == null || messageRegexp.matcher(event.getMessage()).find(); 154 } 155 156 /** 157 * Is matching by xpath query. 158 * 159 * @param event event 160 * @return true if it is matching or not set. 161 */ 162 private boolean isXpathQueryMatching(TreeWalkerAuditEvent event) { 163 boolean isMatching; 164 if (xpathExpression == null) { 165 isMatching = true; 166 } 167 else { 168 isMatching = false; 169 final List<AbstractNode> nodes = getItems(event) 170 .stream().map(AbstractNode.class::cast) 171 .collect(Collectors.toUnmodifiableList()); 172 for (AbstractNode abstractNode : nodes) { 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.getRootAst() == null) { 194 rootNode = null; 195 } 196 else { 197 rootNode = new RootNode(event.getRootAst()); 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 ex) { 206 throw new IllegalStateException("Cannot initialize context and evaluate query: " 207 + xpathQuery, ex); 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}