Reputation: 1028
I'm trying to create a type converter similar to the following:
public interface ConvertToString<T> {
String convert (T value);
}
public class ConvertLongToString implements ConvertToString<Long> {
@Override
public String convert (Long value) {
return value.toString();
}
}
public class Foo<T> {
@Inject
@Any
private Instance<ConvertToString<T>> instance;
private T value;
public String getValue() {
return instance.get().convert(value);
}
public void setValue(T value) {
this.value = value;
}
}
In this example, I'd want an instance of Foo<Long>
to know how to convert the value by injecting the correct converter.
Is this possible using CDI like I've shown or even by iterating the list of instances?
I had in mind to build something similar to the Bean Validation framework where you can create validators of the same annotation for many types (of course you have the @ValidatedBy
...)
Upvotes: 5
Views: 7318
Reputation: 3677
As my comment above states, I do not believe the answer considered correct is correct at all, even if it is using advanced CDI apis to answer you. Looks correct but in effect, I do not think it can possibly correct.
The answer that was flagged with minus, on the other hand, was correct, but way too abstract to be helpful.
His producer has one problem. It cannot possible know the conrete instance to return to an injection point when CDI makes your bean. It lacks the same informaiton the WELD implementers lack at compile time to solve your mistery problem. Because the injection point does not specify the concrete paramterized type needed at compile type. And the instance.get() api is also not helping any forther because it is not passing any source type. So done done.
So that answer looks technically good, but in effect it does not work.
I blieve you can approach the problem in many ways. I can think of at least two right now, i am sure there are others.
You can create a Facade converter that is dumb and the only thing it knows how to is figure out the appropriate converter for the input type. Something like:
pulblic class MyClassThatNeedsAConverter{
@Inject
MyAllMightyFacadeConverterThatKnowsAllConcreteConverters converter;
public void someMethodThatNeedsToConverSomethin(T someTypeThatCanBeConverted){
// now i need to convert this to string
String convertedString = converter.convert(someTypeThatCanBeConverted);
// contiue with my business logic
}
}
So that would be the programmers API. Your framework however would implement this all mighty converter facade.
public class MyAllMightyFacadeConverterThatKnowsAllConcreteConverters {
@Inject
ConvertToStringInterface<String> stringToStringConverter;
@Inject
ConvertToStringInterface<Long> longToStringConverter;
// and on you go with different concreted converters
// CDI can resolve these just fine, just make sure you do not have more than
// one candidate bean
...
public <T> String convert(T whateverTypeThatComesISwallow){
if(whateverTypeThatComesISwallow instaceof Long){
return longToStringConverter.convert((Long) whateverTypeThatComesISwallow );
}
if(whateverTypeThatComesISwallow instaceof SomeOtherType){
// same old story
}
throw new RuntimeException("You are out of luck, this type i do not know off an will not convert" + whateverTypeThatComesISwallow.getClass().getCanonincalName() );
}
}
This is one route.
Another route is you can have something like a converter cache. So your framework could offer something like
@ApplicationScoped
public class MyConverterHolder{
@Inject
Instance<StringConverter<?>> allConverterInstancesKnownToTheSystem;
final Map<Class, StringConveter<?>> myCacheOfConveters = new HashMap<>();
@postConstruct
public postConstruct(){
// you do not want to this initialization all the time
// so you just do it once during construction
for(StringConveter<?> currentConverterInstance : allConverterInstancesKnownToTheSystem) {
myCacheOfConveters.put(currentConverterInstance .getSupportedInputType(), currentConverterInstance );
}
} // post construct ends here
public <T> String convert(T someTypedDataIcanSwallow){
Class<T> sourceType = someTypedDataIcanSwallow.getClass();
return myCacheOfConveters.get(sourceType).convert(someTypedDataIcanSwallow);
}
}
I tend to like rather more the second approach. I dislike spamming code with ifs and elses when an hash map can do the job for me and I do not have to maintain it. But the point is. CDI cannot give you a concrete implementaiton to solve your problem unless you tell it the concrete type. If you do not give it the concrete type, CDI can give you all the instances that match some type criteria. But then you have to solve the problem of picking out the right one.
So you need to create this facade object in between the conversion.
And good read on parameterized tpye injection is of course the spec itself. That describes the matching rules for resolving beans towards an injection point and gives examples after the description. E.g. See section "5.2.4. Assignability of raw and parameterized types".
For example, Dao is eligible for injection to any injection point of
type @Default Dao<Order>, @Default Dao<User>, @Default Dao<?>,
@Default Dao<? extends Persistent> or @Default Dao<X extends
Persistent> where X is a type variable.
And this paragraph is also important:
Any legal bean type, as defined in Legal bean types may be the required type of an injection point. Furthermore, the required type of an injection point may contain a wildcard type parameter. However, a type variable is not a legal injection point type.
If an injection point type is a type variable, the container automatically detects the problem and treats it as a definition error.
Kind regards.
Upvotes: 2
Reputation: 1519
Generics are compile-time only, but with that said you could parse the injection point and have a similar behavior at runtime. Example:
@Inject
@ConvertToString
ConvertToStringInterface<T> converter;
@Produces
@ConvertToString
public ConvertToStringInterface produceConverter(InjectionPoint injectionPoint) {
Type type = injectionPoint.getType();
ParameterizedType parameterizedType = (ParameterizedType) type;
Type argType = parameterizedType.getActualTypeArguments()[0];
Class<?> clazz = (Class<?>) argType;
if (clazz == Long.class) {
return new ConvertLongToString();
}
}
This may need some adjustments but it should work.
Upvotes: 2
Reputation: 1413
Generics are a compile-time construct, the information necessary to make it work for runtime injection isn't there, type erasure just defeats the whole thing....and boy, did I try with Spring!
Upvotes: 0