1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.filters;
21  
22  import java.util.List;
23  import java.util.Objects;
24  import java.util.regex.Pattern;
25  import java.util.stream.Collectors;
26  
27  import com.puppycrawl.tools.checkstyle.TreeWalkerAuditEvent;
28  import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
29  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
30  import com.puppycrawl.tools.checkstyle.xpath.AbstractNode;
31  import com.puppycrawl.tools.checkstyle.xpath.RootNode;
32  import net.sf.saxon.om.Item;
33  import net.sf.saxon.sxpath.XPathDynamicContext;
34  import net.sf.saxon.sxpath.XPathEvaluator;
35  import net.sf.saxon.sxpath.XPathExpression;
36  import net.sf.saxon.trans.XPathException;
37  
38  /**
39   * This filter element is immutable and processes {@link TreeWalkerAuditEvent}
40   * objects based on the criteria of file, check, module id, xpathQuery.
41   *
42   */
43  public class XpathFilterElement implements TreeWalkerFilter {
44  
45      /** The regexp to match file names against. */
46      private final Pattern fileRegexp;
47  
48      /** The pattern for file names. */
49      private final String filePattern;
50  
51      /** The regexp to match check names against. */
52      private final Pattern checkRegexp;
53  
54      /** The pattern for check class names. */
55      private final String checkPattern;
56  
57      /** The regexp to match message names against. */
58      private final Pattern messageRegexp;
59  
60      /** The pattern for message names. */
61      private final String messagePattern;
62  
63      /** Module id filter. */
64      private final String moduleId;
65  
66      /** Xpath expression. */
67      private final XPathExpression xpathExpression;
68  
69      /** Xpath query. */
70      private final String xpathQuery;
71  
72      /**
73       * Creates a {@code XpathElement} instance.
74       * @param files regular expression for names of filtered files
75       * @param checks regular expression for filtered check classes
76       * @param message regular expression for messages.
77       * @param moduleId the module id
78       * @param query the xpath query
79       */
80      public XpathFilterElement(String files, String checks,
81                         String message, String moduleId, String query) {
82          filePattern = files;
83          if (files == null) {
84              fileRegexp = null;
85          }
86          else {
87              fileRegexp = Pattern.compile(files);
88          }
89          checkPattern = checks;
90          if (checks == null) {
91              checkRegexp = null;
92          }
93          else {
94              checkRegexp = CommonUtil.createPattern(checks);
95          }
96          messagePattern = message;
97          if (message == null) {
98              messageRegexp = null;
99          }
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 }