001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 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.Collections;
023import java.util.HashSet;
024import java.util.Objects;
025import java.util.Set;
026
027import com.puppycrawl.tools.checkstyle.AbstractAutomaticBean;
028import com.puppycrawl.tools.checkstyle.TreeWalkerAuditEvent;
029import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
030import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
031import com.puppycrawl.tools.checkstyle.api.ExternalResourceHolder;
032import com.puppycrawl.tools.checkstyle.utils.FilterUtil;
033
034/**
035 * <div>
036 * Filter {@code SuppressionXpathFilter} works as
037 * <a href="https://checkstyle.org/filters/suppressionfilter.html">
038 * SuppressionFilter</a>, but also processes {@code suppress-xpath} elements,
039 * which contain xpath-expressions. Xpath-expressions are queries for
040 * suppressed nodes inside the AST tree.
041 * </div>
042 *
043 * <p>
044 * Currently, filter does not support the following checks:
045 * </p>
046 * <ul id="IncompatibleChecks">
047 * <li>
048 * NoCodeInFile (reason is that AST is not generated for a file not containing code)
049 * </li>
050 * <li>
051 * Regexp (reason is at
052 * <a href="https://github.com/checkstyle/checkstyle/issues/7759#issuecomment-605525287"> #7759</a>)
053 * </li>
054 * <li>
055 * RegexpSinglelineJava (reason is at
056 * <a href="https://github.com/checkstyle/checkstyle/issues/7759#issuecomment-605525287"> #7759</a>)
057 * </li>
058 * </ul>
059 *
060 * <p>
061 * Also, the filter does not support suppressions inside javadoc reported by Javadoc checks:
062 * </p>
063 * <ul id="JavadocChecks">
064 * <li>
065 * AtclauseOrder
066 * </li>
067 * <li>
068 * JavadocBlockTagLocation
069 * </li>
070 * <li>
071 * JavadocMethod
072 * </li>
073 * <li>
074 * JavadocMissingLeadingAsterisk
075 * </li>
076 * <li>
077 * JavadocMissingWhitespaceAfterAsterisk
078 * </li>
079 * <li>
080 * JavadocParagraph
081 * </li>
082 * <li>
083 * JavadocStyle
084 * </li>
085 * <li>
086 * JavadocTagContinuationIndentation
087 * </li>
088 * <li>
089 * JavadocType
090 * </li>
091 * <li>
092 * MissingDeprecated
093 * </li>
094 * <li>
095 * NonEmptyAtclauseDescription
096 * </li>
097 * <li>
098 * RequireEmptyLineBeforeBlockTagGroup
099 * </li>
100 * <li>
101 * SingleLineJavadoc
102 * </li>
103 * <li>
104 * SummaryJavadoc
105 * </li>
106 * <li>
107 * WriteTag
108 * </li>
109 * </ul>
110 *
111 * <p>
112 * Note, that support for these Checks will be available after resolving issue
113 * <a href="https://github.com/checkstyle/checkstyle/issues/5770">#5770</a>.
114 * </p>
115 *
116 * <p>
117 * Currently, filter supports the following xpath axes:
118 * </p>
119 * <ul>
120 * <li>
121 * ancestor
122 * </li>
123 * <li>
124 * ancestor-or-self
125 * </li>
126 * <li>
127 * attribute
128 * </li>
129 * <li>
130 * child
131 * </li>
132 * <li>
133 * descendant
134 * </li>
135 * <li>
136 * descendant-or-self
137 * </li>
138 * <li>
139 * following
140 * </li>
141 * <li>
142 * following-sibling
143 * </li>
144 * <li>
145 * parent
146 * </li>
147 * <li>
148 * preceding
149 * </li>
150 * <li>
151 * preceding-sibling
152 * </li>
153 * <li>
154 * self
155 * </li>
156 * </ul>
157 *
158 * <p>
159 * You can use the command line helper tool to generate xpath suppressions based on your
160 * configuration file and input files. See <a href="https://checkstyle.org/cmdline.html">here</a>
161 * for more details.
162 * </p>
163 *
164 * <p>
165 * Notes:
166 * The suppression file location is checked in following order:
167 * </p>
168 * <ol>
169 * <li>
170 * as a filesystem location
171 * </li>
172 * <li>
173 * if no file found, and the location starts with either {@code http://} or {@code https://},
174 * then it is interpreted as a URL
175 * </li>
176 * <li>
177 * if no file found, then passed to the {@code ClassLoader.getResource()} method.
178 * </li>
179 * </ol>
180 *
181 * <p>
182 * SuppressionXpathFilter can suppress Checks that have Treewalker as parent module.
183 * </p>
184 *
185 * <p>
186 * A <a href="/dtds/suppressions_1_2_xpath_experimental.dtd"><em>suppressions XML
187 * document</em></a> contains a set
188 * of {@code suppress} and {@code suppress-xpath} elements, where
189 * each {@code suppress-xpath} element can have the
190 * following attributes:
191 * </p>
192 * <ul>
193 * <li>
194 * {@code files} -
195 * a <a href="../property_types.html#Pattern">Pattern</a>
196 * matched against the file name associated with an audit
197 * event. It is optional.
198 * </li>
199 * <li>
200 * {@code checks} -
201 * a <a href="../property_types.html#Pattern">Pattern</a>
202 * matched against the name of the check associated with an audit
203 * event. Optional as long as {@code id} or {@code message} is specified.
204 * </li>
205 * <li>
206 * {@code message} -
207 * a <a href="../property_types.html#Pattern">Pattern</a>
208 * matched against the message of the check associated with an audit
209 * event. Optional as long as {@code checks} or {@code id} is specified.
210 * </li>
211 * <li>
212 * {@code id} -
213 * a <a href="../property_types.html#String">String</a>
214 * matched against the ID of the check associated with an audit
215 * event. Optional as long as {@code checks} or {@code message} is specified.
216 * </li>
217 * <li>
218 * {@code query} -
219 * a <a href="../property_types.html#String">String</a>
220 * xpath query. It is optional.
221 * </li>
222 * </ul>
223 *
224 * <p>
225 * Each audit event is checked against
226 * each {@code suppress} and {@code suppress-xpath} element. It is
227 * suppressed if all specified attributes match against the audit
228 * event.
229 * </p>
230 *
231 * <p>
232 * ATTENTION: filtering by message is dependent on runtime locale. If project is running
233 * in different languages it is better to avoid filtering by message.
234 * </p>
235 *
236 * <ul>
237 * <li>
238 * Property {@code file} - Specify the location of the <em>suppressions XML document</em> file.
239 * Type is {@code java.lang.String}.
240 * Default value is {@code null}.
241 * </li>
242 * <li>
243 * Property {@code optional} - Control what to do when the file is not existing.
244 * If optional is set to false the file must exist, or else it ends with error.
245 * On the other hand if optional is true and file is not found, the filter accepts all audit events.
246 * Type is {@code boolean}.
247 * Default value is {@code false}.
248 * </li>
249 * </ul>
250 *
251 * <p>
252 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
253 * </p>
254 *
255 * @since 8.6
256 */
257public class SuppressionXpathFilter extends AbstractAutomaticBean implements
258        TreeWalkerFilter, ExternalResourceHolder {
259
260    /** Set of individual xpath suppresses. */
261    private final Set<TreeWalkerFilter> filters = new HashSet<>();
262
263    /** Specify the location of the <em>suppressions XML document</em> file. */
264    private String file;
265    /**
266     * Control what to do when the file is not existing.
267     * If optional is set to false the file must exist, or else it ends with error.
268     * On the other hand if optional is true and file is not found,
269     * the filter accepts all audit events.
270     */
271    private boolean optional;
272
273    /**
274     * Setter to specify the location of the <em>suppressions XML document</em> file.
275     *
276     * @param fileName name of the suppressions file.
277     * @since 8.6
278     */
279    public void setFile(String fileName) {
280        file = fileName;
281    }
282
283    /**
284     * Setter to control what to do when the file is not existing.
285     * If optional is set to false the file must exist, or else it ends with error.
286     * On the other hand if optional is true and file is not found,
287     * the filter accepts all audit events.
288     *
289     * @param optional tells if config file existence is optional.
290     * @since 8.6
291     */
292    public void setOptional(boolean optional) {
293        this.optional = optional;
294    }
295
296    @Override
297    public boolean equals(Object obj) {
298        if (this == obj) {
299            return true;
300        }
301        if (obj == null || getClass() != obj.getClass()) {
302            return false;
303        }
304        final SuppressionXpathFilter suppressionXpathFilter = (SuppressionXpathFilter) obj;
305        return Objects.equals(filters, suppressionXpathFilter.filters);
306    }
307
308    @Override
309    public int hashCode() {
310        return Objects.hash(filters);
311    }
312
313    @Override
314    public boolean accept(TreeWalkerAuditEvent treeWalkerAuditEvent) {
315        boolean result = true;
316        for (TreeWalkerFilter filter : filters) {
317            if (!filter.accept(treeWalkerAuditEvent)) {
318                result = false;
319                break;
320            }
321        }
322        return result;
323    }
324
325    @Override
326    public Set<String> getExternalResourceLocations() {
327        return Collections.singleton(file);
328    }
329
330    @Override
331    protected void finishLocalSetup() throws CheckstyleException {
332        if (file != null) {
333            if (optional) {
334                if (FilterUtil.isFileExists(file)) {
335                    filters.addAll(SuppressionsLoader.loadXpathSuppressions(file));
336                }
337            }
338            else {
339                filters.addAll(SuppressionsLoader.loadXpathSuppressions(file));
340            }
341        }
342    }
343
344}