1
1
/*******************************************************************************
2
- * Copyright (c) 2023, 2023 Hannes Wellmann and others.
2
+ * Copyright (c) 2023, 2024 Hannes Wellmann and others.
3
3
*
4
4
* This program and the accompanying materials
5
5
* are made available under the terms of the Eclipse Public License 2.0
10
10
*
11
11
* Contributors:
12
12
* Hannes Wellmann - initial API and implementation
13
+ * Hannes Wellmann - support multiple versions of one annotation class
13
14
*******************************************************************************/
14
15
15
16
package org .eclipse .e4 .core .internal .di ;
16
17
17
18
import 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 ;
18
24
import java .lang .reflect .AnnotatedElement ;
25
+ import java .lang .reflect .ParameterizedType ;
19
26
import java .lang .reflect .Type ;
20
- import java .util .ArrayList ;
21
- import java .util .HashMap ;
22
27
import java .util .List ;
23
28
import java .util .Map ;
24
- import java .util .Map .Entry ;
29
+ import java .util .Set ;
30
+ import java .util .concurrent .ConcurrentHashMap ;
25
31
import java .util .function .Function ;
26
32
import java .util .function .Supplier ;
27
33
import org .eclipse .e4 .core .di .IInjector ;
@@ -42,129 +48,135 @@ public class AnnotationLookup {
42
48
private AnnotationLookup () {
43
49
}
44
50
45
- public static record AnnotationProxy (List <Class <? extends Annotation >> classes ) {
51
+ public static record AnnotationProxy (List <String > classes ) {
52
+
46
53
public AnnotationProxy {
47
54
classes = List .copyOf (classes );
48
55
}
49
56
50
57
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 () )) {
53
60
return true ;
54
61
}
55
62
}
56
63
return false ;
57
64
}
58
65
}
59
66
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 ));
81
84
}
82
85
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$
85
88
86
89
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 ());
93
91
}
94
92
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 <>();
99
94
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 );
115
120
}
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 );
123
127
}
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 );
129
128
}
130
129
131
130
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
+ }
138
138
}
139
139
}
140
140
return null ;
141
141
}
142
142
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
+ });
151
162
}
152
163
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 );
160
172
}
161
173
162
174
private static boolean javaxWarningPrinted = false ;
163
175
164
- private static void loadJavaxClass ( Runnable run ) {
176
+ private static boolean canLoadJavaxClass ( String className ) {
165
177
try {
166
178
if (!getSystemPropertyFlag ("eclipse.e4.inject.javax.disabled" , false )) { //$NON-NLS-1$
167
- run . run ();
179
+ Class . forName ( className ); // fails if absent
168
180
if (!javaxWarningPrinted ) {
169
181
if (getSystemPropertyFlag ("eclipse.e4.inject.javax.warning" , true )) { //$NON-NLS-1$
170
182
@ SuppressWarnings ("nls" )
@@ -179,10 +191,12 @@ private static void loadJavaxClass(Runnable run) {
179
191
}
180
192
javaxWarningPrinted = true ;
181
193
}
194
+ return true ;
182
195
}
183
- } catch (NoClassDefFoundError e ) {
196
+ } catch (NoClassDefFoundError | ClassNotFoundException e ) {
184
197
// Ignore exception: javax-annotation seems to be unavailable in the runtime
185
198
}
199
+ return false ;
186
200
}
187
201
188
202
private static boolean getSystemPropertyFlag (String key , boolean defaultValue ) {
0 commit comments