001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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     */
082    public XpathFilterElement(String files, String checks,
083                       String message, String moduleId, String query) {
084        filePattern = files;
085        if (files == null) {
086            fileRegexp = null;
087        }
088        else {
089            fileRegexp = Pattern.compile(files);
090        }
091        checkPattern = checks;
092        if (checks == null) {
093            checkRegexp = null;
094        }
095        else {
096            checkRegexp = CommonUtil.createPattern(checks);
097        }
098        messagePattern = message;
099        if (message == null) {
100            messageRegexp = null;
101        }
102        else {
103            messageRegexp = Pattern.compile(message);
104        }
105        this.moduleId = moduleId;
106        xpathQuery = query;
107        if (xpathQuery == null) {
108            xpathExpression = null;
109        }
110        else {
111            final XPathEvaluator xpathEvaluator = new XPathEvaluator(
112                    Configuration.newConfiguration());
113            try {
114                xpathExpression = xpathEvaluator.createExpression(xpathQuery);
115            }
116            catch (XPathException ex) {
117                throw new IllegalArgumentException("Unexpected xpath query: " + xpathQuery, ex);
118            }
119        }
120    }
121
122    /**
123     * Creates a {@code XpathElement} instance.
124     *
125     * @param files regular expression for names of filtered files
126     * @param checks regular expression for filtered check classes
127     * @param message regular expression for messages.
128     * @param moduleId the module id
129     * @param query the xpath query
130     */
131    public XpathFilterElement(Pattern files, Pattern checks, Pattern message,
132                           String moduleId, String query) {
133        if (files == null) {
134            filePattern = null;
135            fileRegexp = null;
136        }
137        else {
138            filePattern = files.pattern();
139            fileRegexp = files;
140        }
141        if (checks == null) {
142            checkPattern = null;
143            checkRegexp = null;
144        }
145        else {
146            checkPattern = checks.pattern();
147            checkRegexp = checks;
148        }
149        if (message == null) {
150            messagePattern = null;
151            messageRegexp = null;
152        }
153        else {
154            messagePattern = message.pattern();
155            messageRegexp = message;
156        }
157        this.moduleId = moduleId;
158        xpathQuery = query;
159        if (xpathQuery == null) {
160            xpathExpression = null;
161        }
162        else {
163            final XPathEvaluator xpathEvaluator = new XPathEvaluator(
164                    Configuration.newConfiguration());
165            try {
166                xpathExpression = xpathEvaluator.createExpression(xpathQuery);
167            }
168            catch (XPathException ex) {
169                throw new IllegalArgumentException("Incorrect xpath query: " + xpathQuery, ex);
170            }
171        }
172    }
173
174    @Override
175    public boolean accept(TreeWalkerAuditEvent event) {
176        return !isFileNameAndModuleAndModuleNameMatching(event)
177                || !isMessageNameMatching(event)
178                || !isXpathQueryMatching(event);
179    }
180
181    /**
182     * Is matching by file name, module id and Check name.
183     *
184     * @param event event
185     * @return true if it is matching
186     */
187    private boolean isFileNameAndModuleAndModuleNameMatching(TreeWalkerAuditEvent event) {
188        return event.getFileName() != null
189                && (fileRegexp == null || fileRegexp.matcher(event.getFileName()).find())
190                && event.getLocalizedMessage() != null
191                && (moduleId == null || moduleId.equals(event.getModuleId()))
192                && (checkRegexp == null || checkRegexp.matcher(event.getSourceName()).find());
193    }
194
195    /**
196     * Is matching by message.
197     *
198     * @param event event
199     * @return true if it is matching or not set.
200     */
201    private boolean isMessageNameMatching(TreeWalkerAuditEvent event) {
202        return messageRegexp == null || messageRegexp.matcher(event.getMessage()).find();
203    }
204
205    /**
206     * Is matching by xpath query.
207     *
208     * @param event event
209     * @return true if it is matching or not set.
210     */
211    private boolean isXpathQueryMatching(TreeWalkerAuditEvent event) {
212        boolean isMatching;
213        if (xpathExpression == null) {
214            isMatching = true;
215        }
216        else {
217            isMatching = false;
218            final List<AbstractNode> nodes = getItems(event)
219                    .stream().map(AbstractNode.class::cast).collect(Collectors.toList());
220            for (AbstractNode abstractNode : nodes) {
221                isMatching = abstractNode.getTokenType() == event.getTokenType()
222                        && abstractNode.getLineNumber() == event.getLine()
223                        && abstractNode.getColumnNumber() == event.getColumnCharIndex();
224                if (isMatching) {
225                    break;
226                }
227            }
228        }
229        return isMatching;
230    }
231
232    /**
233     * Returns list of nodes matching xpath expression given event.
234     *
235     * @param event {@code TreeWalkerAuditEvent} object
236     * @return list of nodes matching xpath expression given event
237     */
238    private List<Item> getItems(TreeWalkerAuditEvent event) {
239        final RootNode rootNode;
240        if (event.getRootAst() == null) {
241            rootNode = null;
242        }
243        else {
244            rootNode = new RootNode(event.getRootAst());
245        }
246        final List<Item> items;
247        try {
248            final XPathDynamicContext xpathDynamicContext =
249                    xpathExpression.createDynamicContext(rootNode);
250            items = xpathExpression.evaluate(xpathDynamicContext);
251        }
252        catch (XPathException ex) {
253            throw new IllegalStateException("Cannot initialize context and evaluate query: "
254                    + xpathQuery, ex);
255        }
256        return items;
257    }
258
259    @Override
260    public int hashCode() {
261        return Objects.hash(filePattern, checkPattern, messagePattern, moduleId, xpathQuery);
262    }
263
264    @Override
265    public boolean equals(Object other) {
266        if (this == other) {
267            return true;
268        }
269        if (other == null || getClass() != other.getClass()) {
270            return false;
271        }
272        final XpathFilterElement xpathFilter = (XpathFilterElement) other;
273        return Objects.equals(filePattern, xpathFilter.filePattern)
274                && Objects.equals(checkPattern, xpathFilter.checkPattern)
275                && Objects.equals(messagePattern, xpathFilter.messagePattern)
276                && Objects.equals(moduleId, xpathFilter.moduleId)
277                && Objects.equals(xpathQuery, xpathFilter.xpathQuery);
278    }
279
280}