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