JDT AST - 嵌套类之后是否存在方法错误?

时间:2017-05-29 15:45:14

标签: java-8 abstract-syntax-tree eclipse-jdt

我尝试解析java类,以便使用JDT AST使用数据填充对象。大部分时间这都很好。但是,java.util.Locale类似乎存在问题。

虽然其他类按预期进行了解析(据我所知),java.util.Localestatically nested class LanguageRange之后的方法中失败。

现在,我借用了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方法。但是,当访问父级时,很明显计算出了错误的父级。这是因为Locale应该是父母的名字,但它是LanguageRange

输出

method: filter
Parent: LanguageRange

注意假设java.util.Locale类被用作输入。

以前有没有人遇到过这个问题?我如何绕过它,以便安全地确定方法的父级?

更新

我也测试了其他一些类,看起来它们工作得很好。这让人更加困惑。

以下是取自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...

但是,在测试完全相同案例的较小版本时,结果是正确的。

示例类

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

由于示例类Test正在运行,我认为我错过了一些东西。但是什么?

注意我使用AST解析器独立(即我只包含必要的库 - 这意味着我无法访问像{{1}这样的类等等)。

1 个答案:

答案 0 :(得分:0)

最后,我找到了解决问题的方法!我研究了一些,发现了这个answer。虽然它似乎没有解决OP的问题,但它肯定给了我一个重要的暗示。

我相应地更新了我的一个课程(见下文)。

示例

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个,这似乎在某种程度上搞砸了。

无论如何,这不是一个错误,我只是错过了正确设置解析器的选项。

最后但并非最不重要的是,有一些课程,应该适用于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);
    }
}

创建此类的新实例(例如astCreator)并像这样使用它: astCreator.accept(fileReader.read(file), myVisitor);。显而易见,fileReadermyVisitor需要替换为您自己的代码。