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