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