11/*******************************************************************************
2- * Copyright (c) 2023, 2023 Hannes Wellmann and others.
2+ * Copyright (c) 2023, 2024 Hannes Wellmann and others.
33 *
44 * This program and the accompanying materials
55 * are made available under the terms of the Eclipse Public License 2.0
1010 *
1111 * Contributors:
1212 * Hannes Wellmann - initial API and implementation
13+ * Hannes Wellmann - support multiple versions of one annotation class
1314 *******************************************************************************/
1415
1516package org .eclipse .e4 .core .internal .di ;
1617
1718import java .lang .annotation .Annotation ;
19+ import java .lang .invoke .CallSite ;
20+ import java .lang .invoke .LambdaMetafactory ;
21+ import java .lang .invoke .MethodHandle ;
22+ import java .lang .invoke .MethodHandles ;
23+ import java .lang .invoke .MethodType ;
1824import java .lang .reflect .AnnotatedElement ;
25+ import java .lang .reflect .ParameterizedType ;
1926import java .lang .reflect .Type ;
20- import java .util .ArrayList ;
21- import java .util .HashMap ;
2227import java .util .List ;
2328import java .util .Map ;
24- import java .util .Map .Entry ;
29+ import java .util .Set ;
30+ import java .util .concurrent .ConcurrentHashMap ;
2531import java .util .function .Function ;
2632import java .util .function .Supplier ;
2733import org .eclipse .e4 .core .di .IInjector ;
@@ -42,129 +48,135 @@ public class AnnotationLookup {
4248 private AnnotationLookup () {
4349 }
4450
45- public static record AnnotationProxy (List <Class <? extends Annotation >> classes ) {
51+ public static record AnnotationProxy (List <String > classes ) {
52+
4653 public AnnotationProxy {
4754 classes = List .copyOf (classes );
4855 }
4956
5057 public boolean isPresent (AnnotatedElement element ) {
51- for (Class <? extends Annotation > annotationClass : classes ) {
52- if (element . isAnnotationPresent ( annotationClass )) {
58+ for (Annotation annotation : element . getAnnotations () ) {
59+ if (classes . contains ( annotation . annotationType (). getName () )) {
5360 return true ;
5461 }
5562 }
5663 return false ;
5764 }
5865 }
5966
60- static final AnnotationProxy INJECT = createProxyForClasses (jakarta .inject .Inject .class ,
61- () -> javax .inject .Inject .class );
62- static final AnnotationProxy SINGLETON = createProxyForClasses (jakarta .inject .Singleton .class ,
63- () -> javax .inject .Singleton .class );
64- static final AnnotationProxy QUALIFIER = createProxyForClasses (jakarta .inject .Qualifier .class ,
65- () -> javax .inject .Qualifier .class );
66-
67- static final AnnotationProxy PRE_DESTROY = createProxyForClasses (jakarta .annotation .PreDestroy .class ,
68- () -> javax .annotation .PreDestroy .class );
69- public static final AnnotationProxy POST_CONSTRUCT = createProxyForClasses (jakarta .annotation .PostConstruct .class ,
70- () -> javax .annotation .PostConstruct .class );
71-
72- static final AnnotationProxy OPTIONAL = createProxyForClasses (org .eclipse .e4 .core .di .annotations .Optional .class ,
73- null );
74-
75- private static AnnotationProxy createProxyForClasses (Class <? extends Annotation > jakartaAnnotationClass ,
76- Supplier <Class <? extends Annotation >> javaxAnnotationClass ) {
77- List <Class <?>> classes = getAvailableClasses (jakartaAnnotationClass , javaxAnnotationClass );
78- @ SuppressWarnings ({ "rawtypes" , "unchecked" })
79- List <Class <? extends Annotation >> annotationClasses = (List ) classes ;
80- return new AnnotationProxy (annotationClasses );
67+ static final AnnotationProxy INJECT = createProxyForClasses ("jakarta.inject.Inject" , //$NON-NLS-1$
68+ "javax.inject.Inject" ); //$NON-NLS-1$
69+ static final AnnotationProxy SINGLETON = createProxyForClasses ("jakarta.inject.Singleton" , //$NON-NLS-1$
70+ "javax.inject.Singleton" ); //$NON-NLS-1$
71+ static final AnnotationProxy QUALIFIER = createProxyForClasses ("jakarta.inject.Qualifier" , //$NON-NLS-1$
72+ "javax.inject.Qualifier" ); //$NON-NLS-1$
73+
74+ static final AnnotationProxy PRE_DESTROY = createProxyForClasses ("jakarta.annotation.PreDestroy" , //$NON-NLS-1$
75+ "javax.annotation.PreDestroy" ); //$NON-NLS-1$
76+ public static final AnnotationProxy POST_CONSTRUCT = createProxyForClasses ("jakarta.annotation.PostConstruct" , //$NON-NLS-1$
77+ "javax.annotation.PostConstruct" ); //$NON-NLS-1$
78+
79+ static final AnnotationProxy OPTIONAL = createProxyForClasses ("org.eclipse.e4.core.di.annotations.Optional" , null ); //$NON-NLS-1$
80+
81+ private static AnnotationProxy createProxyForClasses (String jakartaAnnotationClass ,
82+ String javaxAnnotationClass ) {
83+ return new AnnotationProxy (getAvailableClasses (jakartaAnnotationClass , javaxAnnotationClass ));
8184 }
8285
83- private static final List < Class <?>> PROVIDER_TYPES = getAvailableClasses ( jakarta . inject . Provider . class ,
84- () -> javax .inject .Provider . class );
86+ private static final Set < String > PROVIDER_TYPES = Set
87+ . copyOf ( getAvailableClasses ( "jakarta.inject.Provider" , " javax.inject.Provider" )); //$NON-NLS-1$//$NON-NLS-2$
8588
8689 static boolean isProvider (Type type ) {
87- for (Class <?> clazz : PROVIDER_TYPES ) {
88- if (clazz .equals (type )) {
89- return true ;
90- }
91- }
92- return false ;
90+ return PROVIDER_TYPES .contains (type .getTypeName ());
9391 }
9492
95- @ FunctionalInterface
96- private interface ProviderFactory {
97- Object create (IObjectDescriptor descriptor , IInjector injector , PrimaryObjectSupplier provider );
98- }
93+ private static final Map <Class <?>, MethodHandle > PROVIDER_FACTORYS = new ConcurrentHashMap <>();
9994
100- private static final ProviderFactory PROVIDER_FACTORY ;
101- static {
102- ProviderFactory factory ;
103- try {
104- /**
105- * This subclass solely exists for the purpose to not require the presence of
106- * the javax.inject.Provider interface in the runtime when the base-class is
107- * loaded. This can be deleted when support for javax is removed form the
108- * E4-injector.
109- */
110- class JavaxCompatibilityProviderImpl <T > extends ProviderImpl <T > implements javax .inject .Provider <T > {
111- public JavaxCompatibilityProviderImpl (IObjectDescriptor descriptor , IInjector injector ,
112- PrimaryObjectSupplier provider ) {
113- super (descriptor , injector , provider );
114- }
95+ public static Object getProvider (IObjectDescriptor descriptor , IInjector injector , PrimaryObjectSupplier provider ) {
96+
97+ Supplier <Object > genericProvider = () -> ((InjectorImpl ) injector ).makeFromProvider (descriptor , provider );
98+
99+ Class <?> providerClass ;
100+ if ((descriptor .getDesiredType () instanceof ParameterizedType parameterizedType
101+ && parameterizedType .getRawType () instanceof Class <?> clazz )) {
102+ providerClass = clazz ;
103+ } else {
104+ throw new IllegalStateException (); // The caller must ensure the providerClass can be extracted
105+ }
106+ // At runtime dynamically create a method-reference that implements the specific
107+ // providerClass 'foo.bar.Provider':
108+ // (foo.bar.Provider) genericProvider::get
109+ MethodHandle factory = PROVIDER_FACTORYS .computeIfAbsent (providerClass , providerType -> {
110+ try {
111+ MethodHandles .Lookup lookup = MethodHandles .lookup ();
112+ MethodType suppliedType = MethodType .methodType (Object .class );
113+ CallSite callSite = LambdaMetafactory .metafactory (lookup , "get" , //$NON-NLS-1$
114+ MethodType .methodType (providerClass , Supplier .class ), suppliedType .erase (), //
115+ lookup .findVirtual (Supplier .class , "get" , MethodType .methodType (Object .class )), //$NON-NLS-1$
116+ suppliedType );
117+ return callSite .getTarget ();
118+ } catch (Exception e ) {
119+ throw new IllegalStateException (e );
115120 }
116- factory = JavaxCompatibilityProviderImpl ::new ;
117- // Attempt to load the class early in order to enforce an early class-loading
118- // and to be able to handle the NoClassDefFoundError below in case
119- // javax-Provider is not available in the runtime:
120- factory .create (null , null , null );
121- } catch (NoClassDefFoundError e ) {
122- factory = ProviderImpl ::new ;
121+ });
122+ try {
123+ Object providerImpl = factory .bindTo (genericProvider ).invoke ();
124+ return providerClass .cast (providerImpl );
125+ } catch (Throwable e ) {
126+ throw new IllegalStateException (e );
123127 }
124- PROVIDER_FACTORY = factory ;
125- }
126-
127- public static Object getProvider (IObjectDescriptor descriptor , IInjector injector , PrimaryObjectSupplier provider ) {
128- return PROVIDER_FACTORY .create (descriptor , injector , provider );
129128 }
130129
131130 public static String getQualifierValue (IObjectDescriptor descriptor ) {
132- var annotations = NAMED_ANNOTATION2VALUE_GETTER .entrySet ();
133- for (Entry <Class <? extends Annotation >, Function <Annotation , String >> entry : annotations ) {
134- Class <? extends Annotation > annotationClass = entry .getKey ();
135- if (descriptor .hasQualifier (annotationClass )) {
136- Annotation namedAnnotation = descriptor .getQualifier (annotationClass );
137- return entry .getValue ().apply (namedAnnotation );
131+ Annotation [] qualifiers = descriptor .getQualifiers ();
132+ if (qualifiers != null ) {
133+ for (Annotation namedAnnotation : qualifiers ) {
134+ Class <? extends Annotation > annotationType = namedAnnotation .annotationType ();
135+ if (NAMED_ANNOTATION_CLASSES .contains (annotationType .getName ())) {
136+ return namedAnnotationValueGetter (annotationType ).apply (namedAnnotation );
137+ }
138138 }
139139 }
140140 return null ;
141141 }
142142
143- private static final Map <Class <? extends Annotation >, Function <Annotation , String >> NAMED_ANNOTATION2VALUE_GETTER ;
144-
145- static {
146- Map <Class <? extends Annotation >, Function <Annotation , String >> annotation2valueGetter = new HashMap <>();
147- annotation2valueGetter .put (jakarta .inject .Named .class , a -> ((jakarta .inject .Named ) a ).value ());
148- loadJavaxClass (
149- () -> annotation2valueGetter .put (javax .inject .Named .class , a -> ((javax .inject .Named ) a ).value ()));
150- NAMED_ANNOTATION2VALUE_GETTER = Map .copyOf (annotation2valueGetter );
143+ private static Function <Annotation , String > namedAnnotationValueGetter (
144+ Class <? extends Annotation > annotationType ) {
145+ return NAMED_ANNOTATION2VALUE_GETTER2 .computeIfAbsent (annotationType , type -> {
146+ try {
147+ // At runtime dynamically create the method-reference: 'foo.bar.Named::value'
148+ // where 'foo.bar.Named' is the passed specific annotationType. Invoking the
149+ // returned Function built from the method reference is much faster than using
150+ // reflection.
151+ MethodHandles .Lookup lookup = MethodHandles .lookup ();
152+ MethodType functionApplySignature = MethodType .methodType (String .class , type );
153+ CallSite site = LambdaMetafactory .metafactory (lookup , "apply" , //$NON-NLS-1$
154+ MethodType .methodType (Function .class ), functionApplySignature .erase (),
155+ lookup .findVirtual (type , "value" , MethodType .methodType (String .class )), //$NON-NLS-1$
156+ functionApplySignature );
157+ return (Function <Annotation , String >) site .getTarget ().invokeExact ();
158+ } catch (Throwable e ) {
159+ throw new IllegalStateException (e );
160+ }
161+ });
151162 }
152163
153- private static List <Class <?>> getAvailableClasses (Class <?> jakartaClass , Supplier <? extends Class <?>> javaxClass ) {
154- List <Class <?>> classes = new ArrayList <>();
155- classes .add (jakartaClass );
156- if (javaxClass != null ) {
157- loadJavaxClass (() -> classes .add (javaxClass .get ()));
158- }
159- return classes ;
164+ private static final Map <Class <? extends Annotation >, Function <Annotation , String >> NAMED_ANNOTATION2VALUE_GETTER2 = new ConcurrentHashMap <>();
165+ private static final Set <String > NAMED_ANNOTATION_CLASSES = Set .of ("jakarta.inject.Named" , "javax.inject.Named" ); //$NON-NLS-1$//$NON-NLS-2$
166+ // TODO: warn about the javax-class?
167+
168+ private static List <String > getAvailableClasses (String jakartaClass , String javaxClass ) {
169+ return javaxClass != null && canLoadJavaxClass (javaxClass ) //
170+ ? List .of (jakartaClass , javaxClass )
171+ : List .of (jakartaClass );
160172 }
161173
162174 private static boolean javaxWarningPrinted = false ;
163175
164- private static void loadJavaxClass ( Runnable run ) {
176+ private static boolean canLoadJavaxClass ( String className ) {
165177 try {
166178 if (!getSystemPropertyFlag ("eclipse.e4.inject.javax.disabled" , false )) { //$NON-NLS-1$
167- run . run ();
179+ Class . forName ( className ); // fails if absent
168180 if (!javaxWarningPrinted ) {
169181 if (getSystemPropertyFlag ("eclipse.e4.inject.javax.warning" , true )) { //$NON-NLS-1$
170182 @ SuppressWarnings ("nls" )
@@ -179,10 +191,12 @@ private static void loadJavaxClass(Runnable run) {
179191 }
180192 javaxWarningPrinted = true ;
181193 }
194+ return true ;
182195 }
183- } catch (NoClassDefFoundError e ) {
196+ } catch (NoClassDefFoundError | ClassNotFoundException e ) {
184197 // Ignore exception: javax-annotation seems to be unavailable in the runtime
185198 }
199+ return false ;
186200 }
187201
188202 private static boolean getSystemPropertyFlag (String key , boolean defaultValue ) {
0 commit comments