001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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#SuppressionFilter">
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 * 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 * <ul>
185 * <li>
186 * Property {@code file} - Specify the location of the <em>suppressions XML document</em> file.
187 * Type is {@code java.lang.String}.
188 * Default value is {@code null}.
189 * </li>
190 * <li>
191 * Property {@code optional} - Control what to do when the file is not existing.
192 * If optional is set to false the file must exist, or else it ends with error.
193 * On the other hand if optional is true and file is not found, the filter accepts all audit events.
194 * Type is {@code boolean}.
195 * Default value is {@code false}.
196 * </li>
197 * </ul>
198 *
199 * <p>
200 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
201 * </p>
202 *
203 * @since 8.6
204 */
205public class SuppressionXpathFilter extends AbstractAutomaticBean implements
206        TreeWalkerFilter, ExternalResourceHolder {
207
208    /** Set of individual xpath suppresses. */
209    private final Set<TreeWalkerFilter> filters = new HashSet<>();
210
211    /** Specify the location of the <em>suppressions XML document</em> file. */
212    private String file;
213    /**
214     * Control what to do when the file is not existing.
215     * If optional is set to false the file must exist, or else it ends with error.
216     * On the other hand if optional is true and file is not found,
217     * the filter accepts all audit events.
218     */
219    private boolean optional;
220
221    /**
222     * Setter to specify the location of the <em>suppressions XML document</em> file.
223     *
224     * @param fileName name of the suppressions file.
225     * @since 8.6
226     */
227    public void setFile(String fileName) {
228        file = fileName;
229    }
230
231    /**
232     * Setter to control what to do when the file is not existing.
233     * If optional is set to false the file must exist, or else it ends with error.
234     * On the other hand if optional is true and file is not found,
235     * the filter accepts all audit events.
236     *
237     * @param optional tells if config file existence is optional.
238     * @since 8.6
239     */
240    public void setOptional(boolean optional) {
241        this.optional = optional;
242    }
243
244    @Override
245    public boolean equals(Object obj) {
246        if (this == obj) {
247            return true;
248        }
249        if (obj == null || getClass() != obj.getClass()) {
250            return false;
251        }
252        final SuppressionXpathFilter suppressionXpathFilter = (SuppressionXpathFilter) obj;
253        return Objects.equals(filters, suppressionXpathFilter.filters);
254    }
255
256    @Override
257    public int hashCode() {
258        return Objects.hash(filters);
259    }
260
261    @Override
262    public boolean accept(TreeWalkerAuditEvent treeWalkerAuditEvent) {
263        boolean result = true;
264        for (TreeWalkerFilter filter : filters) {
265            if (!filter.accept(treeWalkerAuditEvent)) {
266                result = false;
267                break;
268            }
269        }
270        return result;
271    }
272
273    @Override
274    public Set<String> getExternalResourceLocations() {
275        return Collections.singleton(file);
276    }
277
278    @Override
279    protected void finishLocalSetup() throws CheckstyleException {
280        if (file != null) {
281            if (optional) {
282                if (FilterUtil.isFileExists(file)) {
283                    filters.addAll(SuppressionsLoader.loadXpathSuppressions(file));
284                }
285            }
286            else {
287                filters.addAll(SuppressionsLoader.loadXpathSuppressions(file));
288            }
289        }
290    }
291
292}