001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 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.CommonUtils;
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 * @author Timur Tibeyev.
042 */
043public class XpathFilter implements TreeWalkerFilter {
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    /** Module id filter. */
057    private final String moduleId;
058
059    /** Xpath expression. */
060    private final XPathExpression xpathExpression;
061
062    /** Xpath query. */
063    private final String xpathQuery;
064
065    /**
066     * Creates a {@code XpathElement} instance.
067     * @param files regular expression for names of filtered files
068     * @param checks regular expression for filtered check classes
069     * @param moduleId the module id
070     * @param query the xpath query
071     */
072    public XpathFilter(String files, String checks,
073                       String moduleId, String query) {
074        filePattern = files;
075        fileRegexp = Pattern.compile(files);
076        checkPattern = checks;
077        if (checks == null) {
078            checkRegexp = null;
079        }
080        else {
081            checkRegexp = CommonUtils.createPattern(checks);
082        }
083        this.moduleId = moduleId;
084        xpathQuery = query;
085        if (xpathQuery == null) {
086            xpathExpression = null;
087        }
088        else {
089            final XPathEvaluator xpathEvaluator = new XPathEvaluator();
090            try {
091                xpathExpression = xpathEvaluator.createExpression(xpathQuery);
092            }
093            catch (XPathException ex) {
094                throw new IllegalStateException("Unexpected xpath query: " + xpathQuery, ex);
095            }
096        }
097    }
098
099    @Override
100    public boolean accept(TreeWalkerAuditEvent event) {
101        return !isFileNameAndModuleAndCheckNameMatching(event)
102                || !isXpathQueryMatching(event);
103    }
104
105    /**
106     * Is matching by file name, moduleId and Check name.
107     * @param event event
108     * @return true if it is matching
109     */
110    private boolean isFileNameAndModuleAndCheckNameMatching(TreeWalkerAuditEvent event) {
111        return event.getFileName() != null
112                && fileRegexp.matcher(event.getFileName()).find()
113                && event.getLocalizedMessage() != null
114                && (moduleId == null || moduleId.equals(event.getModuleId()))
115                && (checkRegexp == null || checkRegexp.matcher(event.getSourceName()).find());
116    }
117
118    /**
119     * Is matching by xpath query.
120     * @param event event
121     * @return true is matching
122     */
123    private boolean isXpathQueryMatching(TreeWalkerAuditEvent event) {
124        boolean isMatching = false;
125        if (xpathExpression != null) {
126            final List<Item> items = getItems(event);
127            for (Item item : items) {
128                final AbstractNode abstractNode = (AbstractNode) item;
129                isMatching = abstractNode.getTokenType() == event.getTokenType()
130                        && abstractNode.getLineNumber() == event.getLine()
131                        && abstractNode.getColumnNumber() == event.getColumn();
132                if (isMatching) {
133                    break;
134                }
135            }
136        }
137        return isMatching;
138    }
139
140    /**
141     * Returns list of nodes matching xpath expression given event.
142     * @param event {@code TreeWalkerAuditEvent} object
143     * @return list of nodes matching xpath expression given event
144     */
145    private List<Item> getItems(TreeWalkerAuditEvent event) {
146        final RootNode rootNode;
147        if (event.getRootAst() == null) {
148            rootNode = null;
149        }
150        else {
151            rootNode = new RootNode(event.getRootAst());
152        }
153        final List<Item> items;
154        try {
155            final XPathDynamicContext xpathDynamicContext =
156                    xpathExpression.createDynamicContext(rootNode);
157            items = xpathExpression.evaluate(xpathDynamicContext);
158        }
159        catch (XPathException ex) {
160            throw new IllegalStateException("Cannot initialize context and evaluate query: "
161                    + xpathQuery, ex);
162        }
163        return items;
164    }
165
166    @Override
167    public int hashCode() {
168        return Objects.hash(filePattern, checkPattern, moduleId, xpathQuery);
169    }
170
171    @Override
172    public boolean equals(Object other) {
173        if (this == other) {
174            return true;
175        }
176        if (other == null || getClass() != other.getClass()) {
177            return false;
178        }
179        final XpathFilter xpathFilter = (XpathFilter) other;
180        return Objects.equals(filePattern, xpathFilter.filePattern)
181                && Objects.equals(checkPattern, xpathFilter.checkPattern)
182                && Objects.equals(moduleId, xpathFilter.moduleId)
183                && Objects.equals(xpathQuery, xpathFilter.xpathQuery);
184    }
185}