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}