Получение квалифицированного имени класса общего типа с помощью обработчика аннотации Java 6
Я разрабатываю небольшой генератор кода, используя JDK 6 API обработки аннотаций и застреваю, пытаясь получить фактический общий тип поля в классе. Чтобы быть яснее, скажем, у меня есть класс вроде этого:
@MyAnnotation
public class User {
private String id;
private String username;
private String password;
private Set<Role> roles = new HashSet<Role>();
private UserProfile profile;
}
и вот мой класс обработчика аннотаций:
@SupportedAnnotationTypes({ "xxx.MyAnnotation" })
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class MongoDocumentAnnotationProcessor extends AbstractProcessor {
private Types typeUtils = null;
private Elements elementUtils = null;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
typeUtils = processingEnv.getTypeUtils();
elementUtils = processingEnv.getElementUtils();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
debug("Running " + getClass().getSimpleName());
if (roundEnv.processingOver() || annotations.size() == 0) {
return false;
}
for (Element element : roundEnv.getRootElements()) {
if (element.getKind() == ElementKind.CLASS && isAnnotatedWithMongoDocument(element)) {
for (VariableElement variableElement : ElementFilter.fieldsIn(element.getEnclosedElements())) {
String fieldName = variableElement.getSimpleName().toString();
Element innerElement = typeUtils.asElement(variableElement.asType());
String fieldClass = "";
if (innerElement == null) { // Primitive type
PrimitiveType primitiveType = (PrimitiveType) variableElement.asType();
fieldClass = typeUtils.boxedClass(primitiveType).getQualifiedName().toString();
} else {
if (innerElement instanceof TypeElement) {
TypeElement typeElement = (TypeElement) innerElement;
fieldClass = typeElement.getQualifiedName().toString();
TypeElement collectionType = elementUtils.getTypeElement("java.util.Collection");
if (typeUtils.isAssignable(typeElement.asType(), collectionType.asType())) {
TypeVariable typeMirror = (TypeVariable)((DeclaredType)typeElement.asType()).getTypeArguments().get(0);
TypeParameterElement typeParameterElement = (TypeParameterElement) typeUtils.asElement(typeMirror);
// I am stuck here. I don't know how to get the
// full qualified class name of the generic type of
// property 'roles' when the code processes the User
// class as above. What I want to retrieve is the
// 'my.package.Role' value
}
}
}
}
}
}
return false;
}
private boolean isAnnotated(Element element) {
List<? extends AnnotationMirror> annotationMirrors = element.getAnnotationMirrors();
if (annotationMirrors == null || annotationMirrors.size() == 0) return false;
for (AnnotationMirror annotationMirror : annotationMirrors) {
String qualifiedName = ((TypeElement)annotationMirror.getAnnotationType().asElement()).getQualifiedName().toString();
if ("xxx.MyAnnotation".equals(qualifiedName)) return true;
}
return false;
}
}
Любая подсказка была бы очень оценена!
Ответы
Ответ 1
Скопировать-вставить мой оригинальный ответ:
Это, кажется, общий вопрос, поэтому для тех, кто прибывает из Google: есть надежда.
Dagger Проект DI лицензируется в соответствии с лицензией Apache 2.0 и содержит некоторые утилиты для работы с типами в обработчике аннотаций.
В частности, класс Util
можно полностью просмотреть в GitHub (Util.java) и определяет метод public static String typeToString(TypeMirror type)
, Он использует TypeVisitor и некоторые рекурсивные вызовы для создания строкового представления типа. Вот фрагмент для справки:
public static void typeToString(final TypeMirror type, final StringBuilder result, final char innerClassSeparator)
{
type.accept(new SimpleTypeVisitor6<Void, Void>()
{
@Override
public Void visitDeclared(DeclaredType declaredType, Void v)
{
TypeElement typeElement = (TypeElement) declaredType.asElement();
rawTypeToString(result, typeElement, innerClassSeparator);
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
if (!typeArguments.isEmpty())
{
result.append("<");
for (int i = 0; i < typeArguments.size(); i++)
{
if (i != 0)
{
result.append(", ");
}
// NOTE: Recursively resolve the types
typeToString(typeArguments.get(i), result, innerClassSeparator);
}
result.append(">");
}
return null;
}
@Override
public Void visitPrimitive(PrimitiveType primitiveType, Void v) { ... }
@Override
public Void visitArray(ArrayType arrayType, Void v) { ... }
@Override
public Void visitTypeVariable(TypeVariable typeVariable, Void v)
{
result.append(typeVariable.asElement().getSimpleName());
return null;
}
@Override
public Void visitError(ErrorType errorType, Void v) { ... }
@Override
protected Void defaultAction(TypeMirror typeMirror, Void v) { ... }
}, null);
}
Я занят своим собственным проектом, который генерирует расширения класса. Метод Даггера работает для сложных ситуаций, включая общие внутренние классы. У меня есть следующие результаты:
Мой тестовый класс с расширением поля:
public class AnnotationTest
{
...
public static class A
{
@MyAnnotation
private Set<B<Integer>> _bs;
}
public static class B<T>
{
private T _value;
}
}
Вызов метода Dagger на Element
процессор обеспечивает поле _bs
:
accessor.type = DaggerUtils.typeToString(element.asType());
Сгенерированный источник (пользовательский, конечно). Обратите внимание на удивительные вложенные общие типы.
public java.util.Set<AnnotationTest.B<java.lang.Integer>> AnnotationTest.A.getBsGenerated()
{
return this._bs;
}
EDIT: адаптация концепции для извлечения TypeMirror первого общего аргумента, null в противном случае:
public static TypeMirror getGenericType(final TypeMirror type)
{
final TypeMirror[] result = { null };
type.accept(new SimpleTypeVisitor6<Void, Void>()
{
@Override
public Void visitDeclared(DeclaredType declaredType, Void v)
{
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
if (!typeArguments.isEmpty())
{
result[0] = typeArguments.get(0);
}
return null;
}
@Override
public Void visitPrimitive(PrimitiveType primitiveType, Void v)
{
return null;
}
@Override
public Void visitArray(ArrayType arrayType, Void v)
{
return null;
}
@Override
public Void visitTypeVariable(TypeVariable typeVariable, Void v)
{
return null;
}
@Override
public Void visitError(ErrorType errorType, Void v)
{
return null;
}
@Override
protected Void defaultAction(TypeMirror typeMirror, Void v)
{
throw new UnsupportedOperationException();
}
}, null);
return result[0];
}
Ответ 2
Похоже, есть пара проблем. Во-первых, isAssignable() не работает должным образом. Во-вторых, в приведенном выше коде вы пытаетесь получить общие параметры типа Set (T), а не объявление переменной (Role).
Тем не менее, следующий код должен продемонстрировать, что вам нужно:
@SupportedAnnotationTypes({ "xxx.MyAnnotation" })
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class MongoDocumentAnnotationProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver() || annotations.size() == 0) {
return false;
}
for (Element element : roundEnv.getRootElements()) {
if (element.getKind() == ElementKind.CLASS && isAnnotatedWithMongoDocument(element)) {
System.out.println("Running " + getClass().getSimpleName());
for (VariableElement variableElement : ElementFilter.fieldsIn(element.getEnclosedElements())) {
if(variableElement.asType() instanceof DeclaredType){
DeclaredType declaredType = (DeclaredType) variableElement.asType();
for (TypeMirror typeMirror : declaredType.getTypeArguments()) {
System.out.println(typeMirror.toString());
}
}
}
}
}
return true; //processed
}
private boolean isAnnotatedWithMongoDocument(Element element) {
return element.getAnnotation(MyAnnotation.class) != null;
}
}
Этот код должен выводить:
xxx.Role
Ответ 3
Все остальные ответы, имея много хороших точек. На самом деле не показывайте вам проблемы, которые у вас есть, и это решение.
Проблема в вашем коде здесь
TypeElement collectionType = elementUtils.getTypeElement("java.util.Collection");
if (typeUtils.isAssignable(typeElement.asType(), collectionType.asType())) {
...
Ваш тип не распространяется java.util.Collection
, а скорее java.util.Collection<*>
. Перепишите вышеприведенный блок, чтобы отразить это:
WildcardType WILDCARD_TYPE_NULL = this.typeUtils.getWildcardType(null, null);
final TypeElement collectionTypeElement = this.elementUtils.getTypeElement(Collection.class.getName());
TypeMirror[] typex = {WILDCARD_TYPE_NULL};
DeclaredType collectionType=this.typeUtils.getDeclaredType(collectionTypeElement, typex);
if (typeUtils.isAssignable(typeElement.asType(), collectionType)){
...
Это должно заставить его работать