001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2021 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.List;
023import java.util.Objects;
024import java.util.regex.Pattern;
025import java.util.stream.Collectors;
026
027import com.puppycrawl.tools.checkstyle.TreeWalkerAuditEvent;
028import com.puppycrawl.tools.checkstyle.TreeWalkerFilter;
029import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
030import com.puppycrawl.tools.checkstyle.xpath.AbstractNode;
031import com.puppycrawl.tools.checkstyle.xpath.RootNode;
032import net.sf.saxon.Configuration;
033import net.sf.saxon.om.Item;
034import net.sf.saxon.sxpath.XPathDynamicContext;
035import net.sf.saxon.sxpath.XPathEvaluator;
036import net.sf.saxon.sxpath.XPathExpression;
037import net.sf.saxon.trans.XPathException;
038
039/**
040 * This filter element is immutable and processes {@link TreeWalkerAuditEvent}
041 * objects based on the criteria of file, check, module id, xpathQuery.
042 *
043 */
044public class XpathFilterElement implements TreeWalkerFilter {
045
046    /** The regexp to match file names against. */
047    private final Pattern fileRegexp;
048
049    /** The pattern for file names. */
050    private final String filePattern;
051
052    /** The regexp to match check names against. */
053    private final Pattern checkRegexp;
054
055    /** The pattern for check class names. */
056    private final String checkPattern;
057
058    /** The regexp to match message names against. */
059    private final Pattern messageRegexp;
060
061    /** The pattern for message names. */
062    private final String messagePattern;
063
064    /** Module id filter. */
065    private final String moduleId;
066
067    /** Xpath expression. */
068    private final XPathExpression xpathExpression;
069
070    /** Xpath query. */
071    private final String xpathQuery;
072
073    /**
074     * Creates a {@code XpathElement} instance.
075     *
076     * @param files regular expression for names of filtered files
077     * @param checks regular expression for filtered check classes
078     * @param message regular expression for messages.
079     * @param moduleId the module id
080     * @param query the xpath query
081     * @throws IllegalArgumentException if the xpath query is not expected.
082     */
083    public XpathFilterElement(String files, String checks,
084                       String message, String moduleId, String query) {
085        filePattern = files;
086        if (files == null) {
087            fileRegexp = null;
088        }
089        else {
090            fileRegexp = Pattern.compile(files);
091        }
092        checkPattern = checks;
093        if (checks == null) {
094            checkRegexp = null;
095        }
096        else {
097            checkRegexp = CommonUtil.createPattern(checks);
098        }
099        messagePattern = message;
100        if (message == null) {
101            messageRegexp = null;
102        }
103        else {
104            messageRegexp = Pattern.compile(message);
105        }
106        this.moduleId = moduleId;
107        xpathQuery = query;
108        if (xpathQuery == null) {
109            xpathExpression = null;
110        }
111        else {
112            final XPathEvaluator xpathEvaluator = new XPathEvaluator(
113                    Configuration.newConfiguration());
114            try {
115                xpathExpression = xpathEvaluator.createExpression(xpathQuery);
116            }
117            catch (XPathException ex) {
118                throw new IllegalArgumentException("Unexpected xpath query: " + xpathQuery, ex);
119            }
120        }
121    }
122
123    /**
124     * Creates a {@code XpathElement} instance.
125     *
126     * @param files regular expression for names of filtered files
127     * @param checks regular expression for filtered check classes
128     * @param message regular expression for messages.
129     * @param moduleId the module id
130     * @param query the xpath query
131     * @throws IllegalArgumentException if the xpath query is not correct.
132     */
133    public XpathFilterElement(Pattern files, Pattern checks, Pattern message,
134                           String moduleId, String query) {
135        if (files == null) {
136            filePattern = null;
137            fileRegexp = null;
138        }
139        else {
140            filePattern = files.pattern();
141            fileRegexp = files;
142        }
143        if (checks == null) {
144            checkPattern = null;
145            checkRegexp = null;
146        }
147        else {
148            checkPattern = checks.pattern();
149            checkRegexp = checks;
150        }
151        if (message == null) {
152            messagePattern = null;
153            messageRegexp = null;
154        }
155        else {
156            messagePattern = message.pattern();
157            messageRegexp = message;
158        }
159        this.moduleId = moduleId;
160        xpathQuery = query;
161        if (xpathQuery == null) {
162            xpathExpression = null;
163        }
164        else {
165            final XPathEvaluator xpathEvaluator = new XPathEvaluator(
166                    Configuration.newConfiguration());
167            try {
168                xpathExpression = xpathEvaluator.createExpression(xpathQuery);
169            }
170            catch (XPathException ex) {
171                throw new IllegalArgumentException("Incorrect xpath query: " + xpathQuery, ex);
172            }
173        }
174    }
175
176    @Override
177    public boolean accept(TreeWalkerAuditEvent event) {
178        return !isFileNameAndModuleAndModuleNameMatching(event)
179                || !isMessageNameMatching(event)
180                || !isXpathQueryMatching(event);
181    }
182
183    /**
184     * Is matching by file name, module id and Check name.
185     *
186     * @param event event
187     * @return true if it is matching
188     */
189    private boolean isFileNameAndModuleAndModuleNameMatching(TreeWalkerAuditEvent event) {
190        return event.getFileName() != null
191                && (fileRegexp == null || fileRegexp.matcher(event.getFileName()).find())
192                && event.getViolation() != null
193                && (moduleId == null || moduleId.equals(event.getModuleId()))
194                && (checkRegexp == null || checkRegexp.matcher(event.getSourceName()).find());
195    }
196
197    /**
198     * Is matching by message.
199     *
200     * @param event event
201     * @return true if it is matching or not set.
202     */
203    private boolean isMessageNameMatching(TreeWalkerAuditEvent event) {
204        return messageRegexp == null || messageRegexp.matcher(event.getMessage()).find();
205    }
206
207    /**
208     * Is matching by xpath query.
209     *
210     * @param event event
211     * @return true if it is matching or not set.
212     */
213    private boolean isXpathQueryMatching(TreeWalkerAuditEvent event) {
214        boolean isMatching;
215        if (xpathExpression == null) {
216            isMatching = true;
217        }
218        else {
219            isMatching = false;
220            final List<AbstractNode> nodes = getItems(event)
221                    .stream().map(AbstractNode.class::cast).collect(Collectors.toList());
222            for (AbstractNode abstractNode : nodes) {
223                isMatching = abstractNode.getTokenType() == event.getTokenType()
224                        && abstractNode.getLineNumber() == event.getLine()
225                        && abstractNode.getColumnNumber() == event.getColumnCharIndex();
226                if (isMatching) {
227                    break;
228                }
229            }
230        }
231        return isMatching;
232    }
233
234    /**
235     * Returns list of nodes matching xpath expression given event.
236     *
237     * @param event {@code TreeWalkerAuditEvent} object
238     * @return list of nodes matching xpath expression given event
239     * @throws IllegalStateException if the xpath query could not be evaluated.
240     */
241    private List<Item> getItems(TreeWalkerAuditEvent event) {
242        final RootNode rootNode;
243        if (event.getRootAst() == null) {
244            rootNode = null;
245        }
246        else {
247            rootNode = new RootNode(event.getRootAst());
248        }
249        final List<Item> items;
250        try {
251            final XPathDynamicContext xpathDynamicContext =
252                    xpathExpression.createDynamicContext(rootNode);
253            items = xpathExpression.evaluate(xpathDynamicContext);
254        }
255        catch (XPathException ex) {
256            throw new IllegalStateException("Cannot initialize context and evaluate query: "
257                    + xpathQuery, ex);
258        }
259        return items;
260    }
261
262    @Override
263    public int hashCode() {
264        return Objects.hash(filePattern, checkPattern, messagePattern, moduleId, xpathQuery);
265    }
266
267    @Override
268    public boolean equals(Object other) {
269        if (this == other) {
270            return true;
271        }
272        if (other == null || getClass() != other.getClass()) {
273            return false;
274        }
275        final XpathFilterElement xpathFilter = (XpathFilterElement) other;
276        return Objects.equals(filePattern, xpathFilter.filePattern)
277                && Objects.equals(checkPattern, xpathFilter.checkPattern)
278                && Objects.equals(messagePattern, xpathFilter.messagePattern)
279                && Objects.equals(moduleId, xpathFilter.moduleId)
280                && Objects.equals(xpathQuery, xpathFilter.xpathQuery);
281    }
282
283}