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,130 @@ 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 );
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$
66
73
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 );
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$
71
78
72
- static final AnnotationProxy OPTIONAL = createProxyForClasses (org .eclipse .e4 .core .di .annotations .Optional . class ,
79
+ static final AnnotationProxy OPTIONAL = createProxyForClasses (" org.eclipse.e4.core.di.annotations.Optional" , //$NON-NLS-1$
73
80
null );
74
81
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 );
82
+ private static AnnotationProxy createProxyForClasses (String jakartaAnnotationClass ,
83
+ String javaxAnnotationClass ) {
84
+ return new AnnotationProxy (getAvailableClasses (jakartaAnnotationClass , javaxAnnotationClass ));
81
85
}
82
86
83
- private static final List < Class <?>> PROVIDER_TYPES = getAvailableClasses ( jakarta . inject . Provider . class ,
84
- () -> javax .inject .Provider . class );
87
+ private static final Set < String > PROVIDER_TYPES = Set
88
+ . copyOf ( getAvailableClasses ( "jakarta.inject.Provider" , " javax.inject.Provider" )); //$NON-NLS-1$//$NON-NLS-2$
85
89
86
90
static boolean isProvider (Type type ) {
87
- for (Class <?> clazz : PROVIDER_TYPES ) {
88
- if (clazz .equals (type )) {
89
- return true ;
90
- }
91
- }
92
- return false ;
91
+ return PROVIDER_TYPES .contains (type .getTypeName ());
93
92
}
94
93
95
- @ FunctionalInterface
96
- private interface ProviderFactory {
97
- Object create (IObjectDescriptor descriptor , IInjector injector , PrimaryObjectSupplier provider );
98
- }
94
+ private static final Map <Class <?>, MethodHandle > PROVIDER_FACTORYS = new ConcurrentHashMap <>();
99
95
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
- }
96
+ public static Object getProvider (IObjectDescriptor descriptor , IInjector injector , PrimaryObjectSupplier provider ) {
97
+ Class <?> providerClass ;
98
+ Type desiredType = descriptor .getDesiredType ();
99
+ if ((desiredType instanceof ParameterizedType parameterizedType
100
+ && parameterizedType .getRawType () instanceof Class <?> clazz )) {
101
+ providerClass = clazz ;
102
+ } else if (desiredType instanceof Class <?> clazz ) {
103
+ providerClass = clazz ;
104
+ } else {
105
+ throw new IllegalArgumentException ();
106
+ }
107
+ // Dynamically create a lambda implementing the providerClass
108
+ MethodHandle factory = PROVIDER_FACTORYS .computeIfAbsent (providerClass , providerType -> {
109
+ try {
110
+ MethodHandles .Lookup lookup = MethodHandles .lookup ();
111
+ MethodType suppliedType = MethodType .methodType (Object .class );
112
+ CallSite callSite = LambdaMetafactory .metafactory (lookup , "get" , //$NON-NLS-1$
113
+ MethodType .methodType (providerClass , Supplier .class ), suppliedType .erase (), //
114
+ lookup .findVirtual (Supplier .class , "get" , suppliedType ), //$NON-NLS-1$
115
+ suppliedType );
116
+ return callSite .getTarget ();
117
+ } catch (Exception e ) {
118
+ throw new IllegalStateException (e );
115
119
}
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 ;
120
+ });
121
+ try {
122
+ Supplier <Object > genericProvider = () -> ((InjectorImpl ) injector ).makeFromProvider (descriptor , provider );
123
+ return factory .bindTo (genericProvider ).invoke ();
124
+ } catch (Throwable e ) {
125
+ throw new IllegalStateException (e );
123
126
}
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
127
}
130
128
131
129
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 );
130
+ Annotation [] qualifiers = descriptor .getQualifiers ();
131
+ if (qualifiers != null ) {
132
+ for (Annotation namedAnnotation : qualifiers ) {
133
+ Class <? extends Annotation > annotationType = namedAnnotation .annotationType ();
134
+ if (NAMED_ANNOTATION_CLASSES .contains (annotationType .getName ())) {
135
+ return namedAnnotationValueGetter (annotationType ).apply (namedAnnotation );
136
+ }
138
137
}
139
138
}
140
139
return null ;
141
140
}
142
141
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 );
142
+ private static Function <Annotation , String > namedAnnotationValueGetter (
143
+ Class <? extends Annotation > annotationType ) {
144
+ return NAMED_ANNOTATION2VALUE_GETTER2 .computeIfAbsent (annotationType , type -> {
145
+ try {
146
+ MethodHandles .Lookup lookup = MethodHandles .lookup ();
147
+ MethodType functionApplySignature = MethodType .methodType (String .class , type );
148
+ CallSite site = LambdaMetafactory .metafactory (lookup , "apply" , //$NON-NLS-1$
149
+ MethodType .methodType (Function .class ), functionApplySignature .erase (),
150
+ lookup .findVirtual (type , "value" , MethodType .methodType (String .class )), //$NON-NLS-1$
151
+ functionApplySignature );
152
+ return (Function <Annotation , String >) site .getTarget ().invokeExact ();
153
+ } catch (Throwable e ) {
154
+ throw new IllegalStateException (e );
155
+ }
156
+ });
151
157
}
152
158
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 ;
159
+ private static final Map <Class <? extends Annotation >, Function <Annotation , String >> NAMED_ANNOTATION2VALUE_GETTER2 = new ConcurrentHashMap <>();
160
+ private static final Set <String > NAMED_ANNOTATION_CLASSES = Set .of ("jakarta.inject.Named" , "javax.inject.Named" ); //$NON-NLS-1$//$NON-NLS-2$
161
+ // TODO: warn about the javax-class?
162
+
163
+ private static List <String > getAvailableClasses (String jakartaClass , String javaxClass ) {
164
+ return javaxClass != null && canLoadJavaxClass (javaxClass ) //
165
+ ? List .of (jakartaClass , javaxClass )
166
+ : List .of (jakartaClass );
160
167
}
161
168
162
169
private static boolean javaxWarningPrinted = false ;
163
170
164
- private static void loadJavaxClass ( Runnable run ) {
171
+ private static boolean canLoadJavaxClass ( String className ) {
165
172
try {
166
173
if (!getSystemPropertyFlag ("eclipse.e4.inject.javax.disabled" , false )) { //$NON-NLS-1$
167
- run . run ();
174
+ Class . forName ( className ); // fails if absent
168
175
if (!javaxWarningPrinted ) {
169
176
if (getSystemPropertyFlag ("eclipse.e4.inject.javax.warning" , true )) { //$NON-NLS-1$
170
177
@ SuppressWarnings ("nls" )
@@ -179,10 +186,12 @@ private static void loadJavaxClass(Runnable run) {
179
186
}
180
187
javaxWarningPrinted = true ;
181
188
}
189
+ return true ;
182
190
}
183
- } catch (NoClassDefFoundError e ) {
191
+ } catch (NoClassDefFoundError | ClassNotFoundException e ) {
184
192
// Ignore exception: javax-annotation seems to be unavailable in the runtime
185
193
}
194
+ return false ;
186
195
}
187
196
188
197
private static boolean getSystemPropertyFlag (String key , boolean defaultValue ) {
0 commit comments