mg_england mg_england - 18 days ago 5
Java Question

How to find the bounds of a TypeMirror & turn them into a JavaPoet TypeSpec?

I have an annotation processor that takes an annotated class and attempts to create a subclass of it:

package test;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;

@SupportedAnnotationTypes("java.lang.SuppressWarnings")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class BSProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
for (TypeElement baseClassAnnotation : annotations) {
for (Element annotatedElement : roundEnvironment.getElementsAnnotatedWith(baseClassAnnotation)) {
handleAnnotatedTypeElement((TypeElement) annotatedElement);
}
}
return true;
}

private void handleAnnotatedTypeElement(TypeElement annotatedTypeElement) {
try {
javaFile(annotatedTypeElement).writeTo(System.out);
} catch (IOException e) {
e.printStackTrace();
}
}

private JavaFile javaFile(TypeElement annotatedTypeElement) {
return JavaFile.builder(packageName(annotatedTypeElement), typeSpec(annotatedTypeElement))
.build();
}

private TypeSpec typeSpec(TypeElement annotatedTypeElement) {
return TypeSpec.classBuilder(className(annotatedTypeElement))
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.build();
}

private ClassName className(TypeElement annotatedTypeElement) {
return ClassName.get(packageName(annotatedTypeElement), String.format("AutoGenerated_%s",
annotatedTypeElement.getSimpleName()));
}

private String packageName(TypeElement annotatedTypeElement) {
return annotatedTypeElement.getEnclosingElement().toString();
}
}


This works with classes without type parameters, but I'm not sure how to do so with them. Performing
toString
on the type variables will only give the variable name, not the bounds too. Any ideas on how to do this?

Answer

Type parameters of TypeElement can be retrieved by calling getTypeParameters() and their bounds can be obtained by calling getBounds() on obtained TypeParameterElements. I am assuming, that you are troubled over the following step — which concrete types should be passed to those type parameters to satisfy those bounds.

Unfortunately, this is a problem, that can not be easily solved for arbitrary type (which is what you seems to be after). For example, look at this type:

public abstract class Recursive<UU extends Callable<UU>> {
}

You can intuitively conclude, that it can be implemented by a class like this:

public class Solution extends Recursive<Solution> implements Callable<Solution> {
  @Override
  public Solution call() throws Exception {
    return new Solution();
  }
}

but that's not trivial to automate, and you (probably) do not want to include required machinery in your code.

Instead of trying to solve that problem yourself, I recommend you to take advantage of type erasure and let compiler solve the problem for you:

// returns something like "Map<K, V>", this is NOT what you want!
DeclaredType classType = (DeclaredType) typeElement.asType();

Types types = processingEnvironment.getTypeUtils();
Elements elements = processingEnvironment.getElementUtils();

// this obtains raw type (plain "Map"), with all methods type-erased,
// the compiler is much better at solving type riddles than you!
DeclaredType rawType = types.getDeclaredType(typeElement);

final Collection<? extends ExecutableElement> methods =
    ElementFilter.methodsIn(elements.getAllMembers(typeElement));

// To create a MethodSpec, suitable for the raw type, you should 
// call 3-parameter MethodSpec#overriding with type-erased raw class type
// as second parameter and javax.lang.model.util.Types instance as third
MethodSpec newMethod = MethodSpec.overriding(methods.get(0), rawType, types);

As such, the answer to your specific question is "don't pass any type parameters to JavaPoet, use raw types".

Comments