forked from checkstyle/checkstyle
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Issue checkstyle#4901: Implement xpath query generator
- Loading branch information
Showing
4 changed files
with
586 additions
and
1 deletion.
There are no files selected for viewing
229 changes: 229 additions & 0 deletions
229
src/main/java/com/puppycrawl/tools/checkstyle/xpath/XpathQueryGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
//////////////////////////////////////////////////////////////////////////////// | ||
// checkstyle: Checks Java source code for adherence to a set of rules. | ||
// Copyright (C) 2001-2017 the original author or authors. | ||
// | ||
// This library is free software; you can redistribute it and/or | ||
// modify it under the terms of the GNU Lesser General Public | ||
// License as published by the Free Software Foundation; either | ||
// version 2.1 of the License, or (at your option) any later version. | ||
// | ||
// This library is distributed in the hope that it will be useful, | ||
// but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
// Lesser General Public License for more details. | ||
// | ||
// You should have received a copy of the GNU Lesser General Public | ||
// License along with this library; if not, write to the Free Software | ||
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | ||
//////////////////////////////////////////////////////////////////////////////// | ||
|
||
package com.puppycrawl.tools.checkstyle.xpath; | ||
|
||
import java.util.ArrayList; | ||
import java.util.List; | ||
|
||
import com.puppycrawl.tools.checkstyle.api.DetailAST; | ||
import com.puppycrawl.tools.checkstyle.api.TokenTypes; | ||
import com.puppycrawl.tools.checkstyle.utils.TokenUtils; | ||
|
||
/** | ||
* Generates xpath queries. Xpath queries are generated based on received | ||
* {@code DetailAst} element, line number and column number. | ||
* | ||
* <p> | ||
* Example class | ||
* </p> | ||
* <pre> | ||
* public class Main { | ||
* | ||
* public String sayHello(String name) { | ||
* return "Hello, " + name; | ||
* } | ||
* } | ||
* </pre> | ||
* | ||
* <p> | ||
* Following expression returns list of queries. Each query is the string representing full | ||
* path to the node inside Xpath tree, whose line number is 3 and column number is 4. | ||
* </p> | ||
* <pre> | ||
* new XpathQueryGenerator(rootAst, 3, 4).generate(); | ||
* </pre> | ||
* | ||
* <p> | ||
* Result list | ||
* </p> | ||
* <ul> | ||
* <li> | ||
* /CLASS_DEF[@text='Main']/OBJBLOCK/METHOD_DEF[@text='sayHello'] | ||
* </li> | ||
* <li> | ||
* /CLASS_DEF[@text='Main']/OBJBLOCK/METHOD_DEF[@text='sayHello']/MODIFIERS | ||
* </li> | ||
* <li> | ||
* /CLASS_DEF[@text='Main']/OBJBLOCK/METHOD_DEF[@text='sayHello']/MODIFIERS/LITERAL_PUBLIC | ||
* </li> | ||
* </ul> | ||
* | ||
* @author Timur Tibeyev. | ||
*/ | ||
public class XpathQueryGenerator { | ||
/** The root ast. */ | ||
private final DetailAST rootAst; | ||
/** The line number of the element for which the query should be generated. */ | ||
private final int lineNumber; | ||
/** The column number of the element for which the query should be generated. */ | ||
private final int columnNumber; | ||
|
||
/** | ||
* Creates a new {@code XpathQueryGenerator} instance. | ||
* | ||
* @param rootAst root ast | ||
* @param lineNumber line number of the element for which the query should be generated | ||
* @param columnNumber column number of the element for which the query should be generated | ||
*/ | ||
public XpathQueryGenerator(DetailAST rootAst, int lineNumber, int columnNumber) { | ||
this.rootAst = rootAst; | ||
this.lineNumber = lineNumber; | ||
this.columnNumber = columnNumber; | ||
} | ||
|
||
/** | ||
* Returns list of xpath queries of nodes, matching line and column number. | ||
* This approach uses DetailAST traversal. DetailAST means detail abstract syntax tree. | ||
* @return list of xpath queries of nodes, matching line and column number | ||
*/ | ||
public List<String> generate() { | ||
final List<DetailAST> matchingAstElements = getMatchingAstElements(rootAst, lineNumber, | ||
columnNumber); | ||
final List<String> generated = new ArrayList<>(matchingAstElements.size()); | ||
for (DetailAST ast : matchingAstElements) { | ||
final StringBuilder xpathQueryBuilder = new StringBuilder(getXpathQuery(null, ast)); | ||
if (!isUniqueAst(ast)) { | ||
final DetailAST child = findChildWithIdent(ast); | ||
if (child != null) { | ||
xpathQueryBuilder.append("[.") | ||
.append(getXpathQuery(ast, child)) | ||
.append(']'); | ||
} | ||
} | ||
generated.add(xpathQueryBuilder.toString()); | ||
} | ||
return generated; | ||
} | ||
|
||
/** | ||
* Returns child {@code DetailAst} element of the given root, | ||
* which has child element with token type equals to {@link TokenTypes#IDENT}. | ||
* @param root {@code DetailAST} root ast | ||
* @return child {@code DetailAst} element of the given root | ||
*/ | ||
private static DetailAST findChildWithIdent(DetailAST root) { | ||
DetailAST curNode = root; | ||
while (curNode.findFirstToken(TokenTypes.IDENT) == null) { | ||
DetailAST toVisit = curNode.getFirstChild(); | ||
while (toVisit == null) { | ||
toVisit = curNode.getNextSibling(); | ||
if (toVisit == null) { | ||
curNode = curNode.getParent(); | ||
} | ||
} | ||
|
||
if (curNode == root.getParent()) { | ||
curNode = null; | ||
break; | ||
} | ||
|
||
curNode = toVisit; | ||
} | ||
return curNode; | ||
} | ||
|
||
/** | ||
* Returns list of nodes matching defined line and column number. | ||
* @param root {@code DetailAST} root ast | ||
* @param lineNumber line number | ||
* @param columnNumber column number | ||
* @return list of nodes matching defined line and column number | ||
*/ | ||
private static List<DetailAST> getMatchingAstElements(DetailAST root, int lineNumber, | ||
int columnNumber) { | ||
final List<DetailAST> result = new ArrayList<>(); | ||
DetailAST curNode = root; | ||
while (curNode != null && curNode.getLineNo() <= lineNumber) { | ||
if (curNode.getLineNo() == lineNumber && curNode.getColumnNo() == columnNumber | ||
&& curNode.getType() != TokenTypes.IDENT) { | ||
result.add(curNode); | ||
} | ||
DetailAST toVisit = curNode.getFirstChild(); | ||
while (curNode != null && toVisit == null) { | ||
toVisit = curNode.getNextSibling(); | ||
if (toVisit == null) { | ||
curNode = curNode.getParent(); | ||
} | ||
} | ||
|
||
curNode = toVisit; | ||
} | ||
return result; | ||
} | ||
|
||
/** | ||
* Returns full xpath query for given ast element. | ||
* @param root {@code DetailAST} root element | ||
* @param ast {@code DetailAST} ast element | ||
* @return full xpath query for given ast element | ||
*/ | ||
private static String getXpathQuery(DetailAST root, DetailAST ast) { | ||
final StringBuilder result = new StringBuilder(""); | ||
DetailAST cur = ast; | ||
while (cur != root) { | ||
final StringBuilder curNodeQueryBuilder = new StringBuilder("/") | ||
.append(TokenUtils.getTokenName(cur.getType())); | ||
final DetailAST identAst = cur.findFirstToken(TokenTypes.IDENT); | ||
if (identAst != null) { | ||
curNodeQueryBuilder.append("[@text='") | ||
.append(identAst.getText()) | ||
.append("']"); | ||
} | ||
result.insert(0, curNodeQueryBuilder); | ||
cur = cur.getParent(); | ||
} | ||
return result.toString(); | ||
} | ||
|
||
/** | ||
* To be sure that generated xpath query will return exactly required ast element, the element | ||
* should be checked for uniqueness. If ast element has {@link TokenTypes#IDENT} as the child | ||
* or there is no sibling with the same {@code TokenTypes} then element is supposed to be | ||
* unique. This method finds if {@code DetailAst} element is unique. | ||
* @param ast {@code DetailAST} ast element | ||
* @return if {@code DetailAst} element is unique | ||
*/ | ||
private static boolean isUniqueAst(DetailAST ast) { | ||
boolean isOnly = true; | ||
final DetailAST identAst = ast.findFirstToken(TokenTypes.IDENT); | ||
|
||
if (identAst == null) { | ||
DetailAST cur; | ||
if (ast.getParent() == null) { | ||
cur = ast; | ||
while (cur.getPreviousSibling() != null) { | ||
cur = cur.getPreviousSibling(); | ||
} | ||
} | ||
else { | ||
cur = ast.getParent().getFirstChild(); | ||
} | ||
|
||
while (cur != null) { | ||
if (cur != ast && cur.getType() == ast.getType()) { | ||
isOnly = false; | ||
break; | ||
} | ||
cur = cur.getNextSibling(); | ||
} | ||
} | ||
return isOnly; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.