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