JDT AST - есть ли ошибка с методами после вложенных классов?
Я пытаюсь разобрать java
классы, чтобы заполнить мои объекты данными - используя JDT AST. Большую часть времени это работает просто отлично. Тем не менее, кажется, что есть проблема с java.util.Locale
учебный класс.
В то время как другие классы анализируются, как и ожидалось (насколько мне известно), java.util.Locale
терпит неудачу, когда дело доходит до методов непосредственно после статически вложенного класса LanguageRange
,
Теперь я позаимствовал некоторый код из этого вопроса и изменил его в соответствии со своими потребностями для быстрой настройки тестовой среды.
пример
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 (т.е. я просто включил необходимые библиотеки - это означает, что у меня нет доступа к таким классам, как IProject
и тому подобное).
1 ответ
Наконец-то я нашел решение своей проблемы! Я исследовал еще немного и нашел этот ответ. Хотя это, похоже, не решает проблему ОП, оно, несомненно, дало мне важный совет.
Я обновил один из моих классов соответственно (см. Ниже).
пример
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);
, Obviousely, fileReader
а также myVisitor
должны быть заменены вашим собственным кодом.