5 Replies Latest reply on Nov 30, 2011 10:59 PM by ahhughes

    Generic Typed Injection by Class at Runtime, how?

    ahhughes

      Easiest if I exaplain this with a simple example:



      public class MyPlace implements Place { ...}


      public interface PlaceTokenizer<T extends Place> { .... }


      public class MyPlaceTokenizer implements PlaceTokenizer<MyPlace> { ...}


      and I want to use CDI to obtain an instance of PlaceTokenizer<T extends Place> from a given place....


      PlaceTokenizer<T extends Place> getPlaceTokenizer(T thePlace) {
          //HOW can I use CDI to obtain PlaceTokenizer<T extends Place> for 'thePlace'?
      }


      Thanks in advance :)
      --AH

        • 1. Re: Generic Typed Injection by Class at Runtime, how?
          alesj

          See how TypeLiteral is used in CDI.

          • 2. Re: Generic Typed Injection by Class at Runtime, how?
            alesj


            @Inject Instance<PlaceTokenizer> tokenizers;



            PlaceTokenizer<MyPlace> = tokenizers.select(new TypeLiteral<MyPlace>{}).get();

            • 3. Re: Generic Typed Injection by Class at Runtime, how?
              ahhughes

              Hi Ales,


              Thank you for your replies. Unfortunately, I don't think your advice will resolve my problem, because the TypeLiteral annonymous class requires MyPlace and not thePlace.getClass(). Effectively, this still leaves me with only a 'compile time' solution.... and I can solve that with @Inject MyPlaceTokenizer<MyPlace>... erasure shows its head again :'( The only solution I can find at the moment is to associate MyPlace to MyPlaceTokenizer via a @PlaceQualifier(name="MyPlace") qualifier. If I want to secure this any more, I will need to write a CDI extension to scan for each of the Bean Types, and see that there is a 1:1 binding/association.


              headache++;




              Ales Justin wrote on Nov 09, 2011 09:47:



              @Inject Instance<PlaceTokenizer> tokenizers;



              PlaceTokenizer<MyPlace> = tokenizers.select(new TypeLiteral<MyPlace>{}).get();




              Click HELP for text formatting instructions. Then edit this text and check the preview.


              I have two vague ideas floating in my head (and I doubt either will work cleanly):
              1. Qualifiers : The problem here is that if I re-use a common qualifier @PlaceQualifier on all Place objects, then

              • 4. Re: Generic Typed Injection by Class at Runtime, how?
                ahhughes

                whoops, I half deleted what I was going to write at the end... I might as well say it now...


                I have two vague ideas floating in my head (and I doubt either will work cleanly): 1. Qualifiers : The problem here is that if I re-use a common qualifier @PlaceQualifier on all Place objects, then...


                Continued...


                Qualifiers don't help me at compile time. I can't strictly type/bind to MyPlace/MyPlaceTokenizer using qualifiers, because there is nothing from binding YourPlace / MyPlace...


                @PlaceQualifier(placeClass=MyPlace.class)
                public class YourPlaceTokenizer implements PlaceTokenizer<YourPlace> { ... }


                So... tokenizers.select(new PlaceQualifierLiteral(instanceOfMyPlace.getClass())); will return a PlaceTokenizer<YourPlace> and not PlaceTokenizer<MyPlace> This leads to other issues of course :'(


                Other vague idea I had was to try and register the beans with a custom CDI extension. But this is little merit as erasure will again prevent the extension from determining the type of generic arguments at runtime.


                Either way
                headache++;

                • 5. Re: Generic Typed Injection by Class at Runtime, how?
                  ahhughes

                  This is the best I can come up with....




                  Create a Qualifier (binding by Place class)



                  @Qualifier
                  @Target({ METHOD, FIELD, PARAMETER, TYPE })
                  @Retention(RUNTIME)
                  public @interface PlaceTyped {
                       Class<? extends Place> value();
                  }
                  



                  Create a Literal for the Qualifier



                  This is used to obtain a PlaceTyped (Qualifier) at runtime for programmatic lookup.


                  public class PlaceTypedLiteral extends AnnotationLiteral<PlaceTyped> implements
                            PlaceTyped {
                       private final Class<? extends Place> value;
                  
                       public Class<? extends Place> value() {
                            return value;
                       }
                  
                       public PlaceTypedLiteral(Class<? extends Place> value) {
                            this.value = value;
                       }
                  }
                  



                  Example Bean Definition



                  @PlaceTyped(MyPlace.class)
                  public class MyPlaceTokenizer implements PlaceTokenizer<MyPlace> { ...}
                  



                  Programmatic Lookup of PlaceTokenizer for a given Place object (via .class)



                  @Inject
                  @Any
                  Instance<PlaceTokenizer<Place>> allPlaceTokenizers;
                  ...
                  //if place is instance of MyPlace the following should get MyPlaceTokenizer...
                  allPlaceTokenizers.select(new PlaceTypedLiteral(place.getClass())).get();
                  



                  The Good and Bad



                  This is easy (good), but its not type safe (bad)... I could very well have mixed Your and My place in the annotation and generic type, which would no doubt be a bug...


                  @PlaceTyped(YourPlace.class)
                  public class MyPlaceTokenizer implements PlaceTokenizer<MyPlace> { ...}