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 of {@code suppress} and {@code suppress-xpath} elements, where
184 * each {@code suppress-xpath} element can have the following attributes:
185 * </p>
186 * <ul>
187 * <li>
188 * {@code files} - a <a href="https://checkstyle.org/property-types.html#Pattern">Pattern</a>
189 * matched against the file name associated with an audit
190 * event. It is optional. If unmatched, all Unix path separators (/)
191 * are converted to Windows separators (\) and retried.
192 * </li>
193 * <li>
194 * {@code checks} - a <a href="https://checkstyle.org/property-types.html#Pattern">Pattern</a>
195 * matched against the name of the check associated with an audit
196 * event. Optional as long as {@code id} or {@code message} is specified.
197 * </li>
198 * <li>
199 * {@code message} - a <a href="https://checkstyle.org/property-types.html#Pattern">Pattern</a>
200 * matched against the message of the check associated with an audit
201 * event. Optional as long as {@code checks} or {@code id} is specified.
202 * </li>
203 * <li>
204 * {@code id} - a <a href="https://checkstyle.org/property-types.html#String">String</a>
205 * matched against the ID of the check associated with an audit
206 * event. Optional as long as {@code checks} or {@code message} is specified.
207 * </li>
208 * <li>
209 * {@code query} - a <a href="https://checkstyle.org/property-types.html#String">String</a>
210 * xpath query. It is optional.
211 * </li>
212 * </ul>
213 *
214 * <p>
215 * Each audit event is checked against
216 * each {@code suppress} and {@code suppress-xpath} element. It is
217 * suppressed if all specified attributes match against the audit event.
218 * </p>
219 *
220 * <p>
221 * ATTENTION: filtering by message is dependent on runtime locale. If project is running
222 * in different languages it is better to avoid filtering by message.
223 * </p>
224 *
225 * @since 8.6
226 */
227public class SuppressionXpathFilter extends AbstractAutomaticBean implements
228        TreeWalkerFilter, ExternalResourceHolder {
229
230    /** Set of individual xpath suppresses. */
231    private final Set<TreeWalkerFilter> filters = new HashSet<>();
232
233    /** Specify the location of the <em>suppressions XML document</em> file. */
234    private String file;
235    /**
236     * Control what to do when the file is not existing.
237     * If optional is set to false the file must exist, or else it ends with error.
238     * On the other hand if optional is true and file is not found,
239     * the filter accepts all audit events.
240     */
241    private boolean optional;
242
243    /**
244     * Creates a new {@code SuppressionXpathFilter} instance.
245     */
246    public SuppressionXpathFilter() {
247        // no code by default
248    }
249
250    /**
251     * Setter to specify the location of the <em>suppressions XML document</em> file.
252     *
253     * @param fileName name of the suppressions file.
254     * @since 8.6
255     */
256    public void setFile(String fileName) {
257        file = fileName;
258    }
259
260    /**
261     * Setter to control what to do when the file is not existing.
262     * If optional is set to false the file must exist, or else it ends with error.
263     * On the other hand if optional is true and file is not found,
264     * the filter accepts all audit events.
265     *
266     * @param optional tells if config file existence is optional.
267     * @since 8.6
268     */
269    public void setOptional(boolean optional) {
270        this.optional = optional;
271    }
272
273    @Override
274    public boolean equals(Object obj) {
275        if (this == obj) {
276            return true;
277        }
278        if (obj == null || getClass() != obj.getClass()) {
279            return false;
280        }
281        final SuppressionXpathFilter suppressionXpathFilter = (SuppressionXpathFilter) obj;
282        return Objects.equals(filters, suppressionXpathFilter.filters);
283    }
284
285    @Override
286    public int hashCode() {
287        return Objects.hash(filters);
288    }
289
290    @Override
291    public boolean accept(TreeWalkerAuditEvent treeWalkerAuditEvent) {
292        boolean result = true;
293        for (TreeWalkerFilter filter : filters) {
294            if (!filter.accept(treeWalkerAuditEvent)) {
295                result = false;
296                break;
297            }
298        }
299        return result;
300    }
301
302    @Override
303    public Set<String> getExternalResourceLocations() {
304        return Set.of(file);
305    }
306
307    @Override
308    protected void finishLocalSetup() throws CheckstyleException {
309        if (file != null) {
310            if (optional) {
311                if (FilterUtil.isFileExists(file)) {
312                    filters.addAll(SuppressionsLoader.loadXpathSuppressions(file));
313                }
314            }
315            else {
316                filters.addAll(SuppressionsLoader.loadXpathSuppressions(file));
317            }
318        }
319    }
320
321}