JDT AST - 嵌套 类 后的方法是否存在错误?
JDT AST - is there a bug with methods after nested classes?
我正在尝试解析 java
classes 以便用数据填充我的 objects - 使用 JDT AST. Most of the time this works just fine. However, there seems to be an issue with the java.util.Locale
class.
虽然其他 classes 得到了预期的解析(据我所知),但 java.util.Locale
直接在 statically nested class [=20 之后的方法中失败了=].
现在,我从这个 question 中借用了一些代码并对其进行了修改,以满足我快速设置测试环境的需要。
例子
public static void parse(String code) {
ASTParser parser = ASTParser.newParser(AST.JLS8);
parser.setSource(code.toCharArray());
parser.setKind(ASTParser.K_COMPILATION_UNIT);
final CompilationUnit cu = (CompilationUnit) parser.createAST(null);
cu.accept(new ASTVisitor() {
public boolean visit(MethodDeclaration method) {
if(method.getName().toString().equals("filter")){
debug("method", method.getName().getFullyQualifiedName());
if(method.getParent().getNodeType() == ASTNode.TYPE_DECLARATION){
TypeDeclaration parentClass = TypeDeclaration.class.cast(method.getParent());
debug("Parent", parentClass.getName().toString());
}
}
return false;
}
});
}
public static void debug(String ref, String message) {
System.out.println(ref + ": " + message);
}
使用这段代码,会发生完全相同的事情,所以我不太确定是我遗漏了什么还是发现了错误。
至于 发生了什么,filter
方法被检测到,正如预期的那样。但是,当访问 parent 时,很明显计算出了错误的 parent。这是因为 Locale
应该是 parent 的名字,但它是 LanguageRange
.
输出
method: filter
Parent: LanguageRange
注意假设java.util.Locale
class被用作输入。
以前有人遇到过这个问题吗?我将如何绕过它,以便安全地确定方法的 parent?
更新
我也测试了其他一些 classes,它们似乎工作得很好。这使它更加混乱。
下面是取自 Programm Creek 的示例,我再次根据需要对其进行了修改。
样本
package TEST;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import app.configuration.Configuration;
public class ASTTester {
public static void main(String[] args) {
String srcPath = Configuration.app.getPaths().get("api") + "src"; // Absolute path to src folder
String unitName = "Locale.java"; // Name of the file to parse
String path = srcPath + "\java\util\" + unitName; // Absoulte path to the file to parse
File file = new File(path);
String str = "";
try {
str = Files.lines(Paths.get(file.getAbsolutePath())).reduce((l1, l2) -> l1 + System.lineSeparator() + l2).orElse("");
} catch (IOException e) {
e.printStackTrace();
}
ASTParser parser = ASTParser.newParser(AST.JLS8);
parser.setResolveBindings(true);
parser.setKind(ASTParser.K_COMPILATION_UNIT);
parser.setBindingsRecovery(true);
Map options = JavaCore.getOptions();
parser.setCompilerOptions(options);
parser.setUnitName(unitName);
String[] sources = { srcPath };
String[] classpath = {"C:\Program Files\Java\jre1.8.0_121\lib\rt.jar"}; // May need some altering
parser.setEnvironment(classpath, sources, new String[] { "UTF-8"}, true);
parser.setSource(str.toCharArray());
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
if (cu.getAST().hasBindingsRecovery()) {
System.out.println("Binding activated.");
}
TypeFinderVisitor v = new TypeFinderVisitor();
cu.accept(v);
}
}
class TypeFinderVisitor extends ASTVisitor{
public boolean visit(VariableDeclarationStatement node){
for (Iterator<?> iter = node.fragments().iterator(); iter.hasNext();) {
System.out.println("------------------");
VariableDeclarationFragment fragment = (VariableDeclarationFragment) iter.next();
IVariableBinding binding = fragment.resolveBinding();
System.out.println("binding variable declaration: " +binding.getVariableDeclaration());
System.out.println("binding: " +binding);
}
return true;
}
public boolean visit(TypeDeclaration clazz){
ITypeBinding binding = clazz.resolveBinding();
if(binding != null){
System.out.println("################ BINDING ##############");
System.out.println(binding);
System.out.println("##############################");
for (IMethodBinding method : binding.getDeclaredMethods()) {
System.out.println(clazz.getName().toString() + ": " + method.getName().toString());
}
}
return true;
}
}
输出格式不同,但结果相同。
结果
// Omitted...
LanguageRange: LanguageRange
LanguageRange: LanguageRange
LanguageRange: equals
LanguageRange: filter
LanguageRange: filter
LanguageRange: filterTags
LanguageRange: filterTags
LanguageRange: getRange
LanguageRange: getWeight
LanguageRange: hashCode
// Omitted...
但是,当测试完全相同案例的较小版本时,结果是正确的。
例子Class
package TEST;
public class Test {
public void methodBefore(){
}
public static class Inner{
public static void foo(){
}
}
public static class Inner2{
public static void foo2(){
}
}
public void methodAfter(){
}
}
输出
Test: Test
Test: methodAfter
Test: methodBefore
由于示例 class Test
正在运行,我认为我遗漏了什么。但是什么?
注意我使用 AST 解析器独立(即我只包含必要的库 - 这意味着我没有访问权限到 class 之类的 IProject
之类的)。
终于,我找到了解决问题的方法!我进一步研究并发现了这个 answer。虽然它似乎没有解决 OP 的问题,但它确实给了我一个重要的提示。
我相应地更新了我的一个 classes(见下文)。
例子
package TEST;
import java.util.Map;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
public class ASTBug {
public static void parse(String code) {
ASTParser parser = ASTParser.newParser(AST.JLS8);
Map<String, String> options = JavaCore.getOptions();
options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_8);
parser.setCompilerOptions(options);
// Create a compilation unit
parser.setSource(code.toCharArray());
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
cu.accept(new ASTVisitor() {
public boolean visit(TypeDeclaration clazz) {
System.out.println("########### START ############");
for (MethodDeclaration method : clazz.getMethods()) {
System.out.println(clazz.getName().toString() + ": " + method.getName().toString());
}
System.out.println("########### END ############");
return true;
}
});
}
}
注意现在选项设置正确:options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_8);
.
不过,这是有道理的。默认版本是 JavaCore.Version_1_3
(如前面提到的答案中所述)并且由于 java.util.Locale
包含枚举,支持以 JavaCore.VERSION_1_5
开头(同样,在前面提到的答案中提到)解析器搞砸了。或者至少这是我从检查中得出的结论。
然而,起初它仍然没有工作。很快就找到了解决方案,但我不太确定为什么这是必要的(见下文)。我只使用 1 ASTParser
个实例,而不是每个文件 1 个 ,这似乎以某种方式搞砸了。
无论如何,这不是一个错误,我只是错过了正确设置解析器的选项。
最后但同样重要的是,有一些 class,应该适用于 Java 8
及以下。
ASTCreator
package ast;
import java.util.Arrays;
import java.util.Map;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
public class ASTCreator{
/**
* The AST parser for parsing the Java files.
*/
private ASTParser parser;
/**
* The compilation unit create through the AST parser.
*/
private CompilationUnit compilationUnit;
/**
* Creates the parser with the specified setting.
*/
private void create(){
this.parser = ASTParser.newParser(AST.JLS8);
Map<String, String> options = JavaCore.getOptions();
options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_8);
this.parser.setCompilerOptions(options);
}
/**
* Accepts the given visitors, which specify what to do with the parsed Java file.
* @param fileContent the content of the file to parse
* @param visitors the visitors to accept
*/
public void accept(String fileContent, ASTVisitor... visitors){
this.create();
this.parser.setSource(fileContent.toCharArray());
this.compilationUnit = (CompilationUnit) parser.createAST(null);
Arrays.stream(visitors).forEach(this.compilationUnit::accept);
}
}
创建此 class 的新实例(例如 astCreator
)并像这样使用它:
astCreator.accept(fileReader.read(file), myVisitor);
。很明显,fileReader
和 myVisitor
需要用你自己的代码替换。
我正在尝试解析 java
classes 以便用数据填充我的 objects - 使用 JDT AST. Most of the time this works just fine. However, there seems to be an issue with the java.util.Locale
class.
虽然其他 classes 得到了预期的解析(据我所知),但 java.util.Locale
直接在 statically nested class [=20 之后的方法中失败了=].
现在,我从这个 question 中借用了一些代码并对其进行了修改,以满足我快速设置测试环境的需要。
例子
public static void parse(String code) {
ASTParser parser = ASTParser.newParser(AST.JLS8);
parser.setSource(code.toCharArray());
parser.setKind(ASTParser.K_COMPILATION_UNIT);
final CompilationUnit cu = (CompilationUnit) parser.createAST(null);
cu.accept(new ASTVisitor() {
public boolean visit(MethodDeclaration method) {
if(method.getName().toString().equals("filter")){
debug("method", method.getName().getFullyQualifiedName());
if(method.getParent().getNodeType() == ASTNode.TYPE_DECLARATION){
TypeDeclaration parentClass = TypeDeclaration.class.cast(method.getParent());
debug("Parent", parentClass.getName().toString());
}
}
return false;
}
});
}
public static void debug(String ref, String message) {
System.out.println(ref + ": " + message);
}
使用这段代码,会发生完全相同的事情,所以我不太确定是我遗漏了什么还是发现了错误。
至于 发生了什么,filter
方法被检测到,正如预期的那样。但是,当访问 parent 时,很明显计算出了错误的 parent。这是因为 Locale
应该是 parent 的名字,但它是 LanguageRange
.
输出
method: filter
Parent: LanguageRange
注意假设java.util.Locale
class被用作输入。
以前有人遇到过这个问题吗?我将如何绕过它,以便安全地确定方法的 parent?
更新
我也测试了其他一些 classes,它们似乎工作得很好。这使它更加混乱。
下面是取自 Programm Creek 的示例,我再次根据需要对其进行了修改。
样本
package TEST;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import app.configuration.Configuration;
public class ASTTester {
public static void main(String[] args) {
String srcPath = Configuration.app.getPaths().get("api") + "src"; // Absolute path to src folder
String unitName = "Locale.java"; // Name of the file to parse
String path = srcPath + "\java\util\" + unitName; // Absoulte path to the file to parse
File file = new File(path);
String str = "";
try {
str = Files.lines(Paths.get(file.getAbsolutePath())).reduce((l1, l2) -> l1 + System.lineSeparator() + l2).orElse("");
} catch (IOException e) {
e.printStackTrace();
}
ASTParser parser = ASTParser.newParser(AST.JLS8);
parser.setResolveBindings(true);
parser.setKind(ASTParser.K_COMPILATION_UNIT);
parser.setBindingsRecovery(true);
Map options = JavaCore.getOptions();
parser.setCompilerOptions(options);
parser.setUnitName(unitName);
String[] sources = { srcPath };
String[] classpath = {"C:\Program Files\Java\jre1.8.0_121\lib\rt.jar"}; // May need some altering
parser.setEnvironment(classpath, sources, new String[] { "UTF-8"}, true);
parser.setSource(str.toCharArray());
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
if (cu.getAST().hasBindingsRecovery()) {
System.out.println("Binding activated.");
}
TypeFinderVisitor v = new TypeFinderVisitor();
cu.accept(v);
}
}
class TypeFinderVisitor extends ASTVisitor{
public boolean visit(VariableDeclarationStatement node){
for (Iterator<?> iter = node.fragments().iterator(); iter.hasNext();) {
System.out.println("------------------");
VariableDeclarationFragment fragment = (VariableDeclarationFragment) iter.next();
IVariableBinding binding = fragment.resolveBinding();
System.out.println("binding variable declaration: " +binding.getVariableDeclaration());
System.out.println("binding: " +binding);
}
return true;
}
public boolean visit(TypeDeclaration clazz){
ITypeBinding binding = clazz.resolveBinding();
if(binding != null){
System.out.println("################ BINDING ##############");
System.out.println(binding);
System.out.println("##############################");
for (IMethodBinding method : binding.getDeclaredMethods()) {
System.out.println(clazz.getName().toString() + ": " + method.getName().toString());
}
}
return true;
}
}
输出格式不同,但结果相同。
结果
// Omitted...
LanguageRange: LanguageRange
LanguageRange: LanguageRange
LanguageRange: equals
LanguageRange: filter
LanguageRange: filter
LanguageRange: filterTags
LanguageRange: filterTags
LanguageRange: getRange
LanguageRange: getWeight
LanguageRange: hashCode
// Omitted...
但是,当测试完全相同案例的较小版本时,结果是正确的。
例子Class
package TEST;
public class Test {
public void methodBefore(){
}
public static class Inner{
public static void foo(){
}
}
public static class Inner2{
public static void foo2(){
}
}
public void methodAfter(){
}
}
输出
Test: Test
Test: methodAfter
Test: methodBefore
由于示例 class Test
正在运行,我认为我遗漏了什么。但是什么?
注意我使用 AST 解析器独立(即我只包含必要的库 - 这意味着我没有访问权限到 class 之类的 IProject
之类的)。
终于,我找到了解决问题的方法!我进一步研究并发现了这个 answer。虽然它似乎没有解决 OP 的问题,但它确实给了我一个重要的提示。
我相应地更新了我的一个 classes(见下文)。
例子
package TEST;
import java.util.Map;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
public class ASTBug {
public static void parse(String code) {
ASTParser parser = ASTParser.newParser(AST.JLS8);
Map<String, String> options = JavaCore.getOptions();
options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_8);
parser.setCompilerOptions(options);
// Create a compilation unit
parser.setSource(code.toCharArray());
CompilationUnit cu = (CompilationUnit) parser.createAST(null);
cu.accept(new ASTVisitor() {
public boolean visit(TypeDeclaration clazz) {
System.out.println("########### START ############");
for (MethodDeclaration method : clazz.getMethods()) {
System.out.println(clazz.getName().toString() + ": " + method.getName().toString());
}
System.out.println("########### END ############");
return true;
}
});
}
}
注意现在选项设置正确:options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_8);
.
不过,这是有道理的。默认版本是 JavaCore.Version_1_3
(如前面提到的答案中所述)并且由于 java.util.Locale
包含枚举,支持以 JavaCore.VERSION_1_5
开头(同样,在前面提到的答案中提到)解析器搞砸了。或者至少这是我从检查中得出的结论。
然而,起初它仍然没有工作。很快就找到了解决方案,但我不太确定为什么这是必要的(见下文)。我只使用 1 ASTParser
个实例,而不是每个文件 1 个 ,这似乎以某种方式搞砸了。
无论如何,这不是一个错误,我只是错过了正确设置解析器的选项。
最后但同样重要的是,有一些 class,应该适用于 Java 8
及以下。
ASTCreator
package ast;
import java.util.Arrays;
import java.util.Map;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
public class ASTCreator{
/**
* The AST parser for parsing the Java files.
*/
private ASTParser parser;
/**
* The compilation unit create through the AST parser.
*/
private CompilationUnit compilationUnit;
/**
* Creates the parser with the specified setting.
*/
private void create(){
this.parser = ASTParser.newParser(AST.JLS8);
Map<String, String> options = JavaCore.getOptions();
options.put(JavaCore.COMPILER_SOURCE, JavaCore.VERSION_1_8);
this.parser.setCompilerOptions(options);
}
/**
* Accepts the given visitors, which specify what to do with the parsed Java file.
* @param fileContent the content of the file to parse
* @param visitors the visitors to accept
*/
public void accept(String fileContent, ASTVisitor... visitors){
this.create();
this.parser.setSource(fileContent.toCharArray());
this.compilationUnit = (CompilationUnit) parser.createAST(null);
Arrays.stream(visitors).forEach(this.compilationUnit::accept);
}
}
创建此 class 的新实例(例如 astCreator
)并像这样使用它:
astCreator.accept(fileReader.read(file), myVisitor);
。很明显,fileReader
和 myVisitor
需要用你自己的代码替换。