View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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.Optional;
25  import java.util.regex.Pattern;
26  import java.util.stream.Collectors;
27  
28  import com.puppycrawl.tools.checkstyle.TreeWalkerAuditEvent;
29  import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
30  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
31  import com.puppycrawl.tools.checkstyle.xpath.AbstractNode;
32  import com.puppycrawl.tools.checkstyle.xpath.RootNode;
33  import net.sf.saxon.Configuration;
34  import net.sf.saxon.om.Item;
35  import net.sf.saxon.sxpath.XPathDynamicContext;
36  import net.sf.saxon.sxpath.XPathEvaluator;
37  import net.sf.saxon.sxpath.XPathExpression;
38  import net.sf.saxon.trans.XPathException;
39  
40  /**
41   * This filter element is immutable and processes {@link TreeWalkerAuditEvent}
42   * objects based on the criteria of file, check, module id, xpathQuery.
43   *
44   */
45  public class XpathFilterElement implements TreeWalkerFilter {
46  
47      /** The regexp to match file names against. */
48      private final Pattern fileRegexp;
49  
50      /** The regexp to match check names against. */
51      private final Pattern checkRegexp;
52  
53      /** The regexp to match message names against. */
54      private final Pattern messageRegexp;
55  
56      /** Module id filter. */
57      private final String moduleId;
58  
59      /** Xpath expression. */
60      private final XPathExpression xpathExpression;
61  
62      /** Xpath query. */
63      private final String xpathQuery;
64  
65      /** Indicates if all properties are set to null. */
66      private final boolean isEmptyConfig;
67  
68      /**
69       * Creates a {@code XpathElement} instance.
70       *
71       * @param files regular expression for names of filtered files
72       * @param checks regular expression for filtered check classes
73       * @param message regular expression for messages.
74       * @param moduleId the module id
75       * @param query the xpath query
76       * @throws IllegalArgumentException if the xpath query is not expected.
77       */
78      public XpathFilterElement(String files, String checks,
79                         String message, String moduleId, String query) {
80          this(Optional.ofNullable(files).map(Pattern::compile).orElse(null),
81               Optional.ofNullable(checks).map(CommonUtil::createPattern).orElse(null),
82               Optional.ofNullable(message).map(Pattern::compile).orElse(null),
83               moduleId,
84               query);
85      }
86  
87      /**
88       * Creates a {@code XpathElement} instance.
89       *
90       * @param files regular expression for names of filtered files
91       * @param checks regular expression for filtered check classes
92       * @param message regular expression for messages.
93       * @param moduleId the module id
94       * @param query the xpath query
95       * @throws IllegalArgumentException if the xpath query is not correct.
96       */
97      public XpathFilterElement(Pattern files, Pattern checks, Pattern message,
98                             String moduleId, String query) {
99          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 }