Given this method signature, is it possible to implement it? If so, how?
TypeLiteral<MyClass<T, R>> combineTypes(Class<T> typeOne, Class<R> typeTwo) {
// Awesome implementation here
}
Background
I have an interface, ApiHandler<TReq, TRes>
which I am trying to create in a factory, with Guice, given type TReq
. I can also pass in the type TRes
if necessary. Unfortunately, the TReq
and TRes
have no meaningful parent classes or interfaces for reasons beyond my control (they are generated from Apache Thrift).
If you're computing it based on runtime arguments, it's no longer a literal: It's just a Type.
Though you could use Guava's equivalent framework of TypeToken and its utilities, there's a Type utility class built into Guice: com.google.inject.util.Types. Use newParameterizedType(Type rawType, Type... typeArguments)
to create the Type you want, noting that ParameterizedType and Class both implement Type.
static ParameterizedType combineTypes(Class<?> typeOne, Class<?> typeTwo) {
return Types.newParameterizedType(MyClass.class, typeOne, typeTwo);
}
Unfortunately, AbstractModule.bind
and LinkedBindingBuilder.to
don't offer overloads for Type; just Class, TypeLiteral, and Key. Luckily, you can generate a Key reflectively using a Type, using Key.get(Type)
:
bind(Key.get(combineTypes(Foo.class, Bar.class))).to(MyClassFooBar.class);
Note, in this, that ParameterizedType is not itself a parameterized type. This defeats some of the clever generics-based protection that Guice's bind
EDSL offers. To get the above to work, you may need to @SuppressWarnings
, return the raw type Key
, or consider having combineTypes
return a Key<MyClass<T, R>>
(which would require a cast from Key.get(Type)
's return value Key<?>
). If you really must use a TypeLiteral, you can produce a TypeLiteral through Key.getTypeLiteral, but that would also require a cast from TypeLiteral<?>
—and would not be a "type literal" by any meaningful definition.
TypeLiteral
has two constructors; the normal one when used as intended TypeLiteral<MyClass<Foo, Bar>>() {}
, and the unsafe one which is not generic and is created directly: new TypeLiteral(type)
. Looking at the code for Guice's TypeLiteral here, we see the regular constructor uses parameterized.getActualTypeArguments()[0]
for this parameter, where parameterized
is a ParameterizedType
. To make something that matches MyClass<R, T>
, this would be another ParameterizedType
, but with two parameters, not one, and each parameter would be a plain class. You can now create an implementation of the ParameterizedType
interface that fulfills the contract needed, construct a TypeLiteral
, and cast it to the correct return value (it won't have the correct generic type, you'll have to do an unsafe cast).
It looks like this:
TypeLiteral<MyClass<T, R>> combineTypes(Class<T> typeOne, Class<R> typeTwo) {
return new TypeLiteral(new ParameterizedType() {
@RecentlyNonNull
Type[] getActualTypeArguments() {
return new Type[] {typeOne, typeTwo};
}
@RecentlyNonNull
Type getRawType() {
return MyClass.class;
}
Type getOwnerType() {
// this is only needed for nested classes, eg. if this was Foo.Myclass this would return Foo.class.
return null;
}
});
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With