11/*******************************************************************************
2- * Copyright (c) 2023, 2023 Hannes Wellmann and others.
2+ * Copyright (c) 2023, 2026 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 .MethodHandle ;
20+ import java .lang .invoke .MethodHandles ;
21+ import java .lang .invoke .MethodHandles .Lookup ;
22+ import java .lang .invoke .MethodType ;
1823import java .lang .reflect .AnnotatedElement ;
24+ import java .lang .reflect .ParameterizedType ;
25+ import java .lang .reflect .Proxy ;
1926import java .lang .reflect .Type ;
20- import java .util .ArrayList ;
21- import java .util .HashMap ;
22- import java .util .List ;
27+ import java .util .Collections ;
2328import java .util .Map ;
24- import java .util .Map .Entry ;
29+ import java .util .Set ;
30+ import java .util .WeakHashMap ;
2531import java .util .function .Function ;
26- import java .util .function .Supplier ;
27- import org .eclipse .e4 .core .di .IInjector ;
2832import org .eclipse .e4 .core .di .suppliers .IObjectDescriptor ;
2933import org .eclipse .e4 .core .di .suppliers .PrimaryObjectSupplier ;
3034
@@ -42,154 +46,106 @@ public class AnnotationLookup {
4246 private AnnotationLookup () {
4347 }
4448
45- public static record AnnotationProxy (List <Class <? extends Annotation >> classes ) {
46- public AnnotationProxy {
47- classes = List .copyOf (classes );
48- }
49+ public static record AnnotationProxy (Set <String > classes ) {
4950
5051 public boolean isPresent (AnnotatedElement element ) {
51- for (Class <? extends Annotation > annotationClass : classes ) {
52- if (element . isAnnotationPresent ( annotationClass )) {
52+ for (Annotation annotation : element . getAnnotations () ) {
53+ if (isOfAnyTypeIn ( annotation . annotationType (), classes () )) {
5354 return true ;
5455 }
5556 }
5657 return false ;
5758 }
5859 }
5960
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 );
81- }
61+ static final AnnotationProxy INJECT = createProxyForClasses ( //
62+ "jakarta.inject.Inject" , "javax.inject.Inject" ); //$NON-NLS-1$ //$NON-NLS-2$
63+ static final AnnotationProxy SINGLETON = createProxyForClasses ( //
64+ "jakarta.inject.Singleton" , "javax.inject.Singleton" ); //$NON-NLS-1$//$NON-NLS-2$
65+ static final AnnotationProxy QUALIFIER = createProxyForClasses ( //
66+ "jakarta.inject.Qualifier" , "javax.inject.Qualifier" ); //$NON-NLS-1$//$NON-NLS-2$
8267
83- private static final List <Class <?>> PROVIDER_TYPES = getAvailableClasses (jakarta .inject .Provider .class ,
84- () -> javax .inject .Provider .class );
68+ static final AnnotationProxy PRE_DESTROY = createProxyForClasses ( //
69+ "jakarta.annotation.PreDestroy" , "javax.annotation.PreDestroy" ); //$NON-NLS-1$//$NON-NLS-2$
70+ public static final AnnotationProxy POST_CONSTRUCT = createProxyForClasses ( //
71+ "jakarta.annotation.PostConstruct" , "javax.annotation.PostConstruct" ); //$NON-NLS-1$//$NON-NLS-2$
8572
86- static boolean isProvider (Type type ) {
87- for (Class <?> clazz : PROVIDER_TYPES ) {
88- if (clazz .equals (type )) {
89- return true ;
90- }
91- }
92- return false ;
93- }
73+ static final AnnotationProxy OPTIONAL = createProxyForClasses ("org.eclipse.e4.core.di.annotations.Optional" ); //$NON-NLS-1$
9474
95- @ FunctionalInterface
96- private interface ProviderFactory {
97- Object create (IObjectDescriptor descriptor , IInjector injector , PrimaryObjectSupplier provider );
75+ private static AnnotationProxy createProxyForClasses (String ... annotationClasses ) {
76+ return new AnnotationProxy (Set .of (annotationClasses ));
9877 }
9978
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- }
115- }
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 ;
123- }
124- PROVIDER_FACTORY = factory ;
79+ private static final Set <String > PROVIDER_TYPES = Set .of ( //
80+ "jakarta.inject.Provider" , "javax.inject.Provider" ); //$NON-NLS-1$//$NON-NLS-2$
81+
82+ static boolean isProvider (Type type ) {
83+ return PROVIDER_TYPES .contains (type .getTypeName ());
12584 }
12685
127- public static Object getProvider (IObjectDescriptor descriptor , IInjector injector , PrimaryObjectSupplier provider ) {
128- return PROVIDER_FACTORY .create (descriptor , injector , provider );
86+ public static Object getProvider (IObjectDescriptor descriptor , InjectorImpl injector ,
87+ PrimaryObjectSupplier provider ) {
88+ Class <?> providerClass ;
89+ if ((descriptor .getDesiredType () instanceof ParameterizedType parameterizedType
90+ && parameterizedType .getRawType () instanceof Class <?> desiredClass )) {
91+ providerClass = desiredClass ;
92+ } else {
93+ // Caller must ensure that the providerClass can be extracted
94+ throw new IllegalArgumentException ("Failed to obtain provider class from " + descriptor ); //$NON-NLS-1$
95+ }
96+ return Proxy .newProxyInstance (providerClass .getClassLoader (), new Class [] { providerClass },
97+ (proxy , method , args ) -> switch (method .getName ()) {
98+ case "get" -> injector .makeFromProvider (descriptor , provider ); //$NON-NLS-1$
99+ case "hashCode" -> System .identityHashCode (proxy ); //$NON-NLS-1$
100+ case "equals" -> proxy == args [0 ]; //$NON-NLS-1$
101+ case "toString" -> "Proxy for " + descriptor ; //$NON-NLS-1$ //$NON-NLS-2$
102+ default -> throw new UnsupportedOperationException ("Unsupported method: " + method + "()" ); //$NON-NLS-1$ //$NON-NLS-2$
103+ });
129104 }
130105
131106 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 );
107+ Annotation [] qualifiers = descriptor .getQualifiers ();
108+ if (qualifiers != null ) {
109+ for (Annotation annotation : qualifiers ) {
110+ Class <? extends Annotation > annotationType = annotation .annotationType ();
111+ if (isOfAnyTypeIn (annotationType , NAMED_ANNOTATIONS )) {
112+ Function <Annotation , String > getter = NAMED_VALUE_GETTER .computeIfAbsent (annotationType ,
113+ AnnotationLookup ::createValueGetter );
114+ return getter .apply (annotation );
115+ }
138116 }
139117 }
140118 return null ;
141119 }
142120
143- private static final Map <Class <? extends Annotation >, Function <Annotation , String >> NAMED_ANNOTATION2VALUE_GETTER ;
121+ private static final Set <String > NAMED_ANNOTATIONS = Set .of ( //
122+ "jakarta.inject.Named" , "javax.inject.Named" ); //$NON-NLS-1$//$NON-NLS-2$
144123
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 );
151- }
124+ private static final Map <Class <? extends Annotation >, Function <Annotation , String >> NAMED_VALUE_GETTER = Collections
125+ .synchronizedMap (new WeakHashMap <>());
152126
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 ;
160- }
161-
162- private static boolean javaxWarningPrinted = false ;
163-
164- private static void loadJavaxClass (Runnable run ) {
127+ private static Function <Annotation , String > createValueGetter (Class <? extends Annotation > type ) {
128+ MethodHandle handle ;
165129 try {
166- if (!getSystemPropertyFlag ("eclipse.e4.inject.javax.disabled" , false )) { //$NON-NLS-1$
167- run .run ();
168- if (!javaxWarningPrinted ) {
169- if (getSystemPropertyFlag ("eclipse.e4.inject.javax.warning" , true )) { //$NON-NLS-1$
170- @ SuppressWarnings ("nls" )
171- String message = """
172- WARNING: Annotation classes from the 'javax.inject' or 'javax.annotation' package found.
173- It is recommended to migrate to the corresponding replacements in the jakarta namespace.
174- The Eclipse E4 Platform will remove support for those javax-annotations in a future release.
175- To suppress this warning, set the VM property: -Declipse.e4.inject.javax.warning=false
176- To disable processing of 'javax' annotations entirely, set the VM property: -Declipse.e4.inject.javax.disabled=true
177- """ ;
178- System .err .println (message );
179- }
180- javaxWarningPrinted = true ;
181- }
182- }
183- } catch (NoClassDefFoundError e ) {
184- // Ignore exception: javax-annotation seems to be unavailable in the runtime
130+ Lookup lookup = MethodHandles .publicLookup ();
131+ handle = lookup .findVirtual (type , "value" , MethodType .methodType (String .class )); //$NON-NLS-1$
132+ } catch (NoSuchMethodException | IllegalAccessException e ) {
133+ throw new IllegalStateException (e );
185134 }
135+ // Expect an invocation object typed as general Annotation (the specific
136+ // sub-class is unknown at compile-time) to allow more performant invoke exact
137+ MethodHandle methodHandle = handle .asType (handle .type ().changeParameterType (0 , Annotation .class ));
138+ return a -> {
139+ try {
140+ return (String ) methodHandle .invokeExact (a );
141+ } catch (Throwable e ) {
142+ throw new IllegalStateException (e );
143+ }
144+ };
186145 }
187146
188- private static boolean getSystemPropertyFlag (String key , boolean defaultValue ) {
189- String value = System .getProperty (key );
190- return value == null // assume "true" if value is empty (to allow -Dkey as shorthand for -Dkey=true)
191- ? defaultValue
192- : (value .isEmpty () || Boolean .parseBoolean (value ));
147+ private static boolean isOfAnyTypeIn (Class <? extends Annotation > annotationType , Set <String > annotations ) {
148+ return annotations .contains (annotationType .getName ());
193149 }
194150
195151}
0 commit comments