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