Using Java Compiler Tree API to Extract Generics Types
I was looking for some way to extract information about types of elements in Java collections/maps using generics (List<String>, Map<String, MyBean>) so that the users of the Static JSF Expression Validator wouldn't need to declare the type of the elements manually. One possible way to get this information is to process the source codes with the Sun Compiler Tree API, available since JDK 6.
It might be best to go and check the resulting 263 lines of CollectionGenericsTypeExctractor.java now. The code is little ugly, largely due to the API being ugly.
Overview
Most of the code has been copied from the article Source Code Analysis Using Java 6 APIs by Seema Richard (4/2008).
It might be best to go and check the resulting 263 lines of CollectionGenericsTypeExctractor.java now. The code is little ugly, largely due to the API being ugly.
Overview
- JavaCompiler (Compiler API) is used to compile the source codes that should be searched for generics
- A custom annotation provider (Java Annotation API) is used during the compilation to process the sources
- The processor only delegates to a custom TreePathScanner (Sun Compiler Tree API) whose visitMethod extracts all the information from the MethodTree, using some dark magic to get fully qualified type names of the class and return type
- The Compiler must be able to resolve all dependencies (imports) to be able to process the files
- It only works with Sun JDK and its tools.jar must be on the class path (the Compiler and Annotation APIs are a part of Java specification but the Compiler Tree API is not and is thus vendor-specific)
- The code currently doesn't handle getters inside nested/inner classes (it would need to check that it is such a class and replace the last . with $ in the name)
@Override
public Object visitMethod(MethodTree methodTree, Trees trees) {
String typeNameQualified = getEnclosingClassNameIfAvailable(trees);
// Skip or bad stuff happens (case: inside anonymous inner class)
if (typeNameQualified == null) {
return super.visitMethod(methodTree, trees);
}
Tree returnType = methodTree.getReturnType(); // null for void method
if (getter(methodTree) && returnType instanceof ParameterizedTypeTree) {
assert Tree.Kind.PARAMETERIZED_TYPE == returnType.getKind();
ParameterizedTypeTree parametrizedReturnType = (ParameterizedTypeTree) returnType;
TypeCategory category = detectTypeCategory(parametrizedReturnType);
if (category.isCollectionOrMap()) {
Tree valueTypeArgument = parametrizedReturnType.getTypeArguments().get(category.getValueTypeArgumentIdx());
final String qualifiedGenericTypeName = getQualifiedType(valueTypeArgument);
String methodJsfName = getMethodJsfName(methodTree);
System.out.println("FOUND " + typeNameQualified + "." + methodJsfName + ".*=" + qualifiedGenericTypeName);
// Unqualified name: ((IdentifierTree) valueTypeArgument).getName().toString();
}
}
return super.visitMethod(methodTree, trees);
}
Most of the code has been copied from the article Source Code Analysis Using Java 6 APIs by Seema Richard (4/2008).