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