My Java annotation processor is generating an implementation of an interface, which has a MyInterface.foo()
method returning CompletableFuture<String>
. In my processor the ExecutableElement
for that method indicates a getReturnType()
TypeMirror
with a toString()
value of java.util.concurrent.CompletableFuture<java.lang.String>
, so I know I have the right TypeMirror
for the return type.
Now I want to test in code whether this TypeMirror
instance is indeed a CompletableFuture
of some sort. I tried Types.isAssignable(…)
, Types.isSubtype(…)
, and Types.isSameType(…)
, but none seem to work. For example:
TypeElement completableFutureTypeElement = processingEnv.getElementUtils()
.getTypeElement(CompletableFuture.class.getCanonicalName());
boolean isCompletableFutureReturnType = processingEnv.getTypeUtils()
.isSubtype(returnTypeMirror, completableFutureTypeElement.asType());
Am I using the wrong method for checking? Or is the problem that the return type is CompletableFuture<String>
and I'm comparing against the raw type? In any case, how do I simply check to see that non-parameterized (i.e. raw) class of return type is CompletableFuture
? I suppose I could just compare strings, but is there are more type-safe, semantic way?
My related question is how to get a TypeMirror
for a known class, as I did above for completableFutureTypeElement
and then completableFutureTypeElement.asType()
. Is there a more direct way to get a TypeElement
and/or TypeMirror
for a class such as CompletableFuture.class
without doing a lookup on the string form of its name?
It appears that the comparison technique in the question doesn't work because it compares the raw type to the actual parameterized type. If I cast the return TypeMirror
to a DeclaredType
, get its Element
representation, and then convert back to a TypeMirror
, then the comparison works—I suppose because I'm in effect applying erasure to the returned type:
TypeElement completableFutureTypeElement = processingEnv.getElementUtils()
.getTypeElement(CompletableFuture.class.getCanonicalName());
boolean isCompletableFutureReturnType = processingEnv.getTypeUtils()
.isSubtype(((DeclaredType)returnTypeMirror).asElement().asType(),
completableFutureTypeElement.asType());
That's seems to be a bit of a hack, though. Calling processingEnv.getTypeUtils().erasure(returnTypeMirror)
on the return type mirror, which sounds like it would do the same thing, doesn't work, though. Moreover with this approach I can't seem to use isSubtype(…)
or isAssignable(…)
with Future
, even though CompletableFuture
implements Future
.
Instead, the more semantic way seems to go in the other direction: start by creating a TypeMirror
for a parameterized CompletableFuture<?>
against which to compare—as you would do in Java code. You can create a parameterized unbounded wildcard using Types.getDeclaredType(…)
from the TypeElement
already created; along with Types.getWildcard(…)
, passing null
to indicate no extends
or super
bounds (i.e. the ?
wildcard). Then you use isAssignable(…)
to see if the return type is something that can be assigned to the parameterized type you constructed.
TypeElement completableFutureTypeElement = processingEnv.getElementUtils()
.getTypeElement(CompletableFuture.class.getCanonicalName());
DeclaredType completableFutureWildcardTypeElement =
processingEnv.getTypeUtils().getDeclaredType(completableFutureTypeElement,
processingEnv.getTypeUtils().getWildcardType(null, null));
boolean isCompletableFutureReturnType = processingEnv.getTypeUtils()
.isAssignable(returnTypeMirror, completableFutureWildcardTypeElement);
As a bonus, isAssignable(…)
with a return type of CompletableFuture<String>
will work against Future<?>
as well, so that's a good indication that this second approach of creating parameterized unbounded wildcard types is the more semantic technique.