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.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
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 XpathFilter(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 = CommonUtils.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 IllegalStateException("Unexpected xpath query: " + xpathQuery, ex);
115            }
116        }
117    }
118
119    @Override
120    public boolean accept(TreeWalkerAuditEvent event) {
121        return !isFileNameAndModuleAndCheckNameMatching(event)
122                || !isMessageNameMatching(event)
123                || !isXpathQueryMatching(event);
124    }
125
126    /**
127     * Is matching by file name, moduleId and Check name.
128     * @param event event
129     * @return true if it is matching
130     */
131    private boolean isFileNameAndModuleAndCheckNameMatching(TreeWalkerAuditEvent event) {
132        return event.getFileName() != null
133                && (fileRegexp == null || fileRegexp.matcher(event.getFileName()).find())
134                && event.getLocalizedMessage() != null
135                && (moduleId == null || moduleId.equals(event.getModuleId()))
136                && (checkRegexp == null || checkRegexp.matcher(event.getSourceName()).find());
137    }
138
139    /**
140     * Is matching by message.
141     * @param event event
142     * @return true is matching or not set.
143     */
144    private boolean isMessageNameMatching(TreeWalkerAuditEvent event) {
145        return messageRegexp == null || messageRegexp.matcher(event.getMessage()).find();
146    }
147
148    /**
149     * Is matching by xpath query.
150     * @param event event
151     * @return true is matching
152     */
153    private boolean isXpathQueryMatching(TreeWalkerAuditEvent event) {
154        boolean isMatching;
155        if (xpathExpression == null) {
156            isMatching = true;
157        }
158        else {
159            isMatching = false;
160            final List<Item> items = getItems(event);
161            for (Item item : items) {
162                final AbstractNode abstractNode = (AbstractNode) item;
163                isMatching = abstractNode.getTokenType() == event.getTokenType()
164                        && abstractNode.getLineNumber() == event.getLine()
165                        && abstractNode.getColumnNumber() == event.getColumnCharIndex();
166                if (isMatching) {
167                    break;
168                }
169            }
170        }
171        return isMatching;
172    }
173
174    /**
175     * Returns list of nodes matching xpath expression given event.
176     * @param event {@code TreeWalkerAuditEvent} object
177     * @return list of nodes matching xpath expression given event
178     */
179    private List<Item> getItems(TreeWalkerAuditEvent event) {
180        final RootNode rootNode;
181        if (event.getRootAst() == null) {
182            rootNode = null;
183        }
184        else {
185            rootNode = new RootNode(event.getRootAst());
186        }
187        final List<Item> items;
188        try {
189            final XPathDynamicContext xpathDynamicContext =
190                    xpathExpression.createDynamicContext(rootNode);
191            items = xpathExpression.evaluate(xpathDynamicContext);
192        }
193        catch (XPathException ex) {
194            throw new IllegalStateException("Cannot initialize context and evaluate query: "
195                    + xpathQuery, ex);
196        }
197        return items;
198    }
199
200    @Override
201    public int hashCode() {
202        return Objects.hash(filePattern, checkPattern, messagePattern, moduleId, xpathQuery);
203    }
204
205    @Override
206    public boolean equals(Object other) {
207        if (this == other) {
208            return true;
209        }
210        if (other == null || getClass() != other.getClass()) {
211            return false;
212        }
213        final XpathFilter xpathFilter = (XpathFilter) other;
214        return Objects.equals(filePattern, xpathFilter.filePattern)
215                && Objects.equals(checkPattern, xpathFilter.checkPattern)
216                && Objects.equals(messagePattern, xpathFilter.messagePattern)
217                && Objects.equals(moduleId, xpathFilter.moduleId)
218                && Objects.equals(xpathQuery, xpathFilter.xpathQuery);
219    }
220
221}