Java编译时类解析
在Java中,如何在编译时获得特定标识符的类引用/类型元素 假设我有以下源文件,我想获得对Java编译时类解析,java,classloader,Java,Classloader,在Java中,如何在编译时获得特定标识符的类引用/类型元素 假设我有以下源文件,我想获得对Pixel类的引用,这样我就可以得到它的成员字段列表 package com.foo.bar; class Test { class Pixel { int x,y,r,g,b; } Pixel saturate(Pixel p, int value) {...} } Pixel类定义可以嵌套在Test类中,也可以包含在源不可用的其他包中 我正在使用javax.to
Pixel
类的引用,这样我就可以得到它的成员字段列表
package com.foo.bar;
class Test {
class Pixel {
int x,y,r,g,b;
}
Pixel saturate(Pixel p, int value) {...}
}
Pixel
类定义可以嵌套在Test
类中,也可以包含在源不可用的其他包中
我正在使用javax.tools
API编译源文件,我定义了visitor方法,以便查看每个函数的参数。函数的参数可以使用VariableTree var:node.getParameters()
进行迭代,但是var.getType()
中的类型信息只会触发类名称的visitIdentifier
。此标识符只是简单的名称Pixel
,而不是完全限定的com.foo.bar.Pixel
我需要一种方法将此标识符反转为Pixel.class
或TypeElement
以定义Pixel
类,或完全限定的com.foo.bar.Pixel
字符串,这样我就可以在其上使用类加载器
一种粗略的方法是记录所有类定义,然后尝试进行编译时类型查找,但这对外部定义的类不起作用。据我记忆所及,
var.getType().toString()
返回完全限定的类名。不幸的是,我现在不能检查它,但是你自己试试
是的,我知道,将
toString()
用于除日志记录之外的其他内容是一种非常糟糕的风格,但似乎他们没有给我们其他选择 我最终创建了自己的类查找工具。对于那些感兴趣的人,我将把它包括在这里
使用要包含在搜索中的每个源文件的路径名调用此命令:
public void populateClassDefinitions(String path) {
Iterable<? extends JavaFileObject> files = fileManager.getJavaFileObjects(path);
CompilationTask task =
compiler.getTask(null, fileManager, diagnosticsCollector, null, null, files);
final JavacTask javacTask = (JavacTask) task;
parseResult = null;
try {
parseResult = javacTask.parse();
} catch (IOException e) {
e.printStackTrace();
return;
}
for (CompilationUnitTree tree : parseResult) {
tree.accept(new TreeScanner<Void, Void>() {
@Override
public Void visitCompilationUnit(CompilationUnitTree node, Void p) {
currentPackage = "";
ExpressionTree packageName = node.getPackageName();
if (packageName != null) {
String packageNameString = String.valueOf(packageName);
if (packageNameString.length() > 0) {
currentPackage = packageNameString;
}
}
TreeScanner<Void, String> visitor = new TreeScanner<Void, String>() {
@Override
public Void visitClass(ClassTree node, String packagePrefix) {
if (classDefinitions.get(currentPackage) == null) {
classDefinitions.put(currentPackage, new HashMap<String, ClassTree>());
}
classDefinitions.get(currentPackage).put(packagePrefix + node.getSimpleName(), node);
return super.visitClass(node, packagePrefix + node.getSimpleName() + ".");
}
};
for (Tree decls : node.getTypeDecls()) {
decls.accept(visitor, "");
}
return super.visitCompilationUnit(node, p);
}
}, null);
}
}
public void populateClassDefinitions(字符串路径){
Iterableunfortionally toString()只返回Pixel
,即使Pixel
被移动到另一个包中的另一个文件中也是如此。不过,我不得不将toString()
与其他一些javax.tools类一起使用,所以我知道你的意思。
/**
* Lookup the definition of a class.
*
* Lookup order: 1. Search in the current file: within the current class scope upwards to the
* root. 2. Search laterally across files with the same package value for implicitly included
* classes. 3. Check all import statements.
*
* @param pack
* Current package ex "edu.illinois.crhc"
* @param scope
* Current scope ex "Test.InnerClass"
* @param identifier
* The partial class name to search for
* @return ClassTree the definition of this class if found
*/
ClassLookup lookupClass(CompilationUnitTree packTree, String scope, String identifier) {
dumpClassTable();
String pack = packTree.getPackageName().toString();
System.out.println("Looking for class " + pack + " - " + scope + " - " + identifier);
// Search nested scope and within same package
HashMap<String, ClassTree> packClasses = classDefinitions.get(pack);
if (packClasses != null) {
String[] scopeWalk = scope.split("\\.");
for (int i = scopeWalk.length; i >= 0; i--) {
StringBuilder scopeTest = new StringBuilder();
for (int j = 0; j < i; j++) {
scopeTest.append(scopeWalk[j] + ".");
}
scopeTest.append(identifier);
System.out.println("Testing scope " + pack + " - " + scopeTest.toString());
if (packClasses.containsKey(scopeTest.toString())) {
return new ClassLookup(packClasses.get(scopeTest.toString()), pack.replace(".", "/")
+ "/" + scopeTest.toString().replace(".", "$"));
}
}
}
/*
* Check if fully-qualified identifier (foo.bar.Widget) is used. This needs to search all
* combinations of package and class nesting.
*/
StringBuilder packTest = new StringBuilder();
String[] qualifiedName = identifier.split("\\.");
for (int i = 0; i < qualifiedName.length - 1; i++) {
packTest.append(qualifiedName[i]);
if (i != qualifiedName.length - 2) {
packTest.append(".");
}
}
String clazz = qualifiedName[qualifiedName.length - 1];
System.out.println("Testing absolute identifier: " + packTest.toString() + " " + clazz);
if (classDefinitions.containsKey(packTest.toString())) {
HashMap<String, ClassTree> foundPack = classDefinitions.get(packTest.toString());
if (foundPack.containsKey(clazz)) {
return new ClassLookup(foundPack.get(clazz), packTest.toString().replace(".", "/") + "/"
+ clazz.replace(".", "$"));
}
}
/*
* Search import statements. Last identifier segment must be class name. Search all of the
* packages for the identifier by splitting off the class name. a.b.c.Tree Tree.Branch
* Tree.Branch.Leaf
*/
for (ImportTree imp : currentPackTree.getImports()) {
pack = imp.getQualifiedIdentifier().toString();
System.out.println(pack);
String[] importName = pack.split("\\.");
// Split off class name.
// TODO: (edge case) no package
StringBuilder importTest = new StringBuilder();
for (int i = 0; i < importName.length - 1; i++) {
importTest.append(importName[i]);
if (i != importName.length - 2) {
importTest.append(".");
}
}
// See if the last import segment is * or matches the first segment of the identifier.
System.out.println("Testing globally " + importTest.toString() + " - " + identifier);
if (classDefinitions.containsKey(importTest.toString())) {
HashMap<String, ClassTree> foundPack = classDefinitions.get(importTest.toString());
String[] identifierParts = identifier.split(".");
String importClass = importName[importName.length-1];
if (importClass.equals("*") || identifierParts[0].equals(importClass)) {
if (foundPack.containsKey(identifier)) {
return new ClassLookup(foundPack.get(identifier), importTest.toString().replace(".", "/")
+ "/" + identifier.replace(".", "$"));
}
}
}
}
return null;
}