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.utils;
021
022import java.io.File;
023import java.io.IOException;
024import java.util.Arrays;
025import java.util.List;
026import java.util.Locale;
027import java.util.stream.Collectors;
028
029import com.puppycrawl.tools.checkstyle.AstTreeStringPrinter;
030import com.puppycrawl.tools.checkstyle.JavaParser;
031import com.puppycrawl.tools.checkstyle.api.CheckstyleException;
032import com.puppycrawl.tools.checkstyle.api.DetailAST;
033import com.puppycrawl.tools.checkstyle.api.TokenTypes;
034import com.puppycrawl.tools.checkstyle.xpath.AbstractNode;
035import com.puppycrawl.tools.checkstyle.xpath.RootNode;
036import net.sf.saxon.om.Item;
037import net.sf.saxon.sxpath.XPathDynamicContext;
038import net.sf.saxon.sxpath.XPathEvaluator;
039import net.sf.saxon.sxpath.XPathExpression;
040import net.sf.saxon.trans.XPathException;
041
042/**
043 * Contains utility methods for xpath.
044 *
045 */
046public final class XpathUtil {
047
048    /**
049     * List of token types which support text attribute.
050     * These token types were selected based on analysis that all others do not match required
051     * criteria - text attribute of the token must be useful and help to retrieve more precise
052     * results.
053     * There are three types of AST tokens:
054     * 1. Tokens for which the texts are equal to the name of the token. Or in other words,
055     * nodes for which the following expression is always true:
056     * <pre>
057     *     detailAst.getText().equals(TokenUtil.getTokenName(detailAst.getType()))
058     * </pre>
059     * For example:
060     * <pre>
061     *     //MODIFIERS[@text='MODIFIERS']
062     *     //OBJBLOCK[@text='OBJBLOCK']
063     * </pre>
064     * These tokens do not match required criteria because their texts do not carry any additional
065     * information, they do not affect the xpath requests and do not help to get more accurate
066     * results. The texts of these nodes are useless. No matter what code you analyze, these
067     * texts are always the same.
068     * In addition, they make xpath queries more complex, less readable and verbose.
069     * 2. Tokens for which the texts differ from token names, but texts are always constant.
070     * For example:
071     * <pre>
072     *     //LITERAL_VOID[@text='void']
073     *     //RCURLY[@text='}']
074     * </pre>
075     * These tokens are not used for the same reasons as were described in the previous part.
076     * 3. Tokens for which texts are not constant. The texts of these nodes are closely related
077     * to a concrete class, method, variable and so on.
078     * For example:
079     * <pre>
080     *     String greeting = "HelloWorld";
081     *     //STRING_LITERAL[@text='HelloWorld']
082     * </pre>
083     * <pre>
084     *     int year = 2017;
085     *     //NUM_INT[@text=2017]
086     * </pre>
087     * <pre>
088     *     int age = 23;
089     *     //NUM_INT[@text=23]
090     * </pre>
091     * As you can see same {@code NUM_INT} token type can have different texts, depending on
092     * context.
093     * <pre>
094     *     public class MyClass {}
095     *     //IDENT[@text='MyClass']
096     * </pre>
097     * Only these tokens support text attribute because they make our xpath queries more accurate.
098     * These token types are listed below.
099     * */
100    private static final List<Integer> TOKEN_TYPES_WITH_TEXT_ATTRIBUTE = Arrays.asList(
101            TokenTypes.IDENT, TokenTypes.STRING_LITERAL, TokenTypes.CHAR_LITERAL,
102            TokenTypes.NUM_LONG, TokenTypes.NUM_INT, TokenTypes.NUM_DOUBLE, TokenTypes.NUM_FLOAT);
103
104    /** Delimiter to separate xpath results. */
105    private static final String DELIMITER = "---------" + System.lineSeparator();
106
107    /** Stop instances being created. **/
108    private XpathUtil() {
109    }
110
111    /**
112     * Checks, if specified node can have {@code @text} attribute.
113     *
114     * @param ast {@code DetailAst} element
115     * @return true if element supports {@code @text} attribute, false otherwise
116     */
117    public static boolean supportsTextAttribute(DetailAST ast) {
118        return TOKEN_TYPES_WITH_TEXT_ATTRIBUTE.contains(ast.getType());
119    }
120
121    /**
122     * Returns content of the text attribute of the ast element.
123     *
124     * @param ast {@code DetailAst} element
125     * @return text attribute of the ast element
126     */
127    public static String getTextAttributeValue(DetailAST ast) {
128        String text = ast.getText();
129        if (ast.getType() == TokenTypes.STRING_LITERAL) {
130            text = text.substring(1, text.length() - 1);
131        }
132        return text;
133    }
134
135    /**
136     * Returns xpath query results on file as string.
137     *
138     * @param xpath query to evaluate
139     * @param file file to run on
140     * @return all results as string separated by delimiter
141     * @throws CheckstyleException if some parsing error happens
142     * @throws IOException if an error occurs
143     */
144    public static String printXpathBranch(String xpath, File file) throws CheckstyleException,
145            IOException {
146        final XPathEvaluator xpathEvaluator = new XPathEvaluator();
147        try {
148            final RootNode rootNode = new RootNode(JavaParser.parseFile(file,
149                JavaParser.Options.WITH_COMMENTS));
150            final XPathExpression xpathExpression = xpathEvaluator.createExpression(xpath);
151            final XPathDynamicContext xpathDynamicContext =
152                xpathExpression.createDynamicContext(rootNode);
153            final List<Item<?>> matchingItems = xpathExpression.evaluate(xpathDynamicContext);
154            return matchingItems.stream()
155                .map(item -> ((AbstractNode) item).getUnderlyingNode())
156                .map(AstTreeStringPrinter::printBranch)
157                .collect(Collectors.joining(DELIMITER));
158        }
159        catch (XPathException ex) {
160            final String errMsg = String.format(Locale.ROOT,
161                "Error during evaluation for xpath: %s, file: %s", xpath, file.getCanonicalPath());
162            throw new CheckstyleException(errMsg, ex);
163        }
164    }
165
166}