Skip to content

Commit 995deaf

Browse files
committed
Support multiple versions of annotation classes in E4 Injector
and specifically support jakarta.annotation version 3.0. Fixes #1565
1 parent 1524ff1 commit 995deaf

File tree

3 files changed

+112
-178
lines changed

3 files changed

+112
-178
lines changed

runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/AnnotationLookup.java

+105-91
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
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
@@ -10,18 +10,24 @@
1010
*
1111
* Contributors:
1212
* Hannes Wellmann - initial API and implementation
13+
* Hannes Wellmann - support multiple versions of one annotation class
1314
*******************************************************************************/
1415

1516
package org.eclipse.e4.core.internal.di;
1617

1718
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;
1824
import java.lang.reflect.AnnotatedElement;
25+
import java.lang.reflect.ParameterizedType;
1926
import java.lang.reflect.Type;
20-
import java.util.ArrayList;
21-
import java.util.HashMap;
2227
import java.util.List;
2328
import java.util.Map;
24-
import java.util.Map.Entry;
29+
import java.util.Set;
30+
import java.util.concurrent.ConcurrentHashMap;
2531
import java.util.function.Function;
2632
import java.util.function.Supplier;
2733
import 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) {

runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/InjectorImpl.java

+7-44
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import java.lang.reflect.Modifier;
2525
import java.lang.reflect.ParameterizedType;
2626
import java.lang.reflect.Type;
27-
import java.security.CodeSource;
2827
import java.util.ArrayList;
2928
import java.util.Arrays;
3029
import java.util.Collections;
@@ -37,7 +36,6 @@
3736
import java.util.Map;
3837
import java.util.Set;
3938
import java.util.WeakHashMap;
40-
import java.util.stream.Collectors;
4139
import java.util.stream.Stream;
4240
import org.eclipse.e4.core.di.IBinding;
4341
import org.eclipse.e4.core.di.IInjector;
@@ -50,8 +48,6 @@
5048
import org.eclipse.e4.core.di.suppliers.PrimaryObjectSupplier;
5149
import org.eclipse.e4.core.internal.di.AnnotationLookup.AnnotationProxy;
5250
import org.eclipse.e4.core.internal.di.osgi.LogHelper;
53-
import org.osgi.framework.Bundle;
54-
import org.osgi.framework.FrameworkUtil;
5551

5652
/**
5753
* Reflection-based dependency injector.
@@ -330,7 +326,7 @@ public Object makeFromProvider(IObjectDescriptor descriptor, PrimaryObjectSuppli
330326
Binding binding = findBinding(descriptor);
331327
Class<?> implementationClass;
332328
if (binding == null)
333-
implementationClass = getProviderType(descriptor.getDesiredType());
329+
implementationClass = getProvidedType(descriptor.getDesiredType());
334330
else
335331
implementationClass = binding.getImplementationClass();
336332
if (objectSupplier != null) {
@@ -486,7 +482,7 @@ private Object[] resolveArgs(Requestor<?> requestor, PrimaryObjectSupplier objec
486482

487483
// 1) check if we have a Provider<T>
488484
for (int i = 0; i < actualArgs.length; i++) {
489-
Class<?> providerClass = getProviderType(descriptors[i].getDesiredType());
485+
Class<?> providerClass = getProvidedType(descriptors[i].getDesiredType());
490486
if (providerClass == null) {
491487
continue;
492488
}
@@ -837,14 +833,14 @@ private Class<?> getDesiredClass(Type desiredType) {
837833
/**
838834
* Returns null if not a provider
839835
*/
840-
private Class<?> getProviderType(Type type) {
841-
if (!(type instanceof ParameterizedType))
836+
private Class<?> getProvidedType(Type type) {
837+
if (!(type instanceof ParameterizedType parameterizedType))
842838
return null;
843-
Type rawType = ((ParameterizedType) type).getRawType();
839+
Type rawType = parameterizedType.getRawType();
844840
if (!AnnotationLookup.isProvider(rawType)) {
845841
return null;
846842
}
847-
Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments();
843+
Type[] actualTypes = parameterizedType.getActualTypeArguments();
848844
if (actualTypes.length != 1)
849845
return null;
850846
if (!(actualTypes[0] instanceof Class<?>))
@@ -883,7 +879,7 @@ public IBinding addBinding(IBinding binding) {
883879
}
884880

885881
private Binding findBinding(IObjectDescriptor descriptor) {
886-
Class<?> desiredClass = getProviderType(descriptor.getDesiredType());
882+
Class<?> desiredClass = getProvidedType(descriptor.getDesiredType());
887883
if (desiredClass == null)
888884
desiredClass = getDesiredClass(descriptor.getDesiredType());
889885
synchronized (bindings) {
@@ -942,23 +938,6 @@ private void processAnnotated(AnnotationProxy annotation, Object userObject, Cla
942938
Method[] methods = getDeclaredMethods(objectClass);
943939
for (Method method : methods) {
944940
if (!isAnnotationPresent(method, annotation)) {
945-
if (shouldDebug) {
946-
for (Annotation a : method.getAnnotations()) {
947-
if (annotation.classes().stream().map(Class::getName)
948-
.anyMatch(a.annotationType().getName()::equals)) {
949-
StringBuilder tmp = new StringBuilder();
950-
tmp.append("Possbible annotation mismatch: method \""); //$NON-NLS-1$
951-
tmp.append(method.toString());
952-
tmp.append("\" annotated with \""); //$NON-NLS-1$
953-
tmp.append(describeClass(a.annotationType()));
954-
tmp.append("\" but was looking for \""); //$NON-NLS-1$
955-
tmp.append(annotation.classes().stream().map(InjectorImpl::describeClass)
956-
.collect(Collectors.joining(System.lineSeparator() + " or "))); //$NON-NLS-1$
957-
tmp.append("\""); //$NON-NLS-1$
958-
LogHelper.logWarning(tmp.toString(), null);
959-
}
960-
}
961-
}
962941
continue;
963942
}
964943
if (isOverridden(method, classHierarchy))
@@ -978,22 +957,6 @@ private void processAnnotated(AnnotationProxy annotation, Object userObject, Cla
978957
}
979958
}
980959

981-
/** Provide a human-meaningful description of the provided class */
982-
private static String describeClass(Class<?> cl) {
983-
Bundle b = FrameworkUtil.getBundle(cl);
984-
if (b != null) {
985-
return b.getSymbolicName() + ":" + b.getVersion() + ":" + cl.getName(); //$NON-NLS-1$ //$NON-NLS-2$
986-
}
987-
CodeSource clazzCS = cl.getProtectionDomain().getCodeSource();
988-
if (clazzCS != null) {
989-
return clazzCS.getLocation() + ">" + cl.getName(); //$NON-NLS-1$
990-
}
991-
if (cl.getClassLoader() == null) {
992-
return cl.getName() + " [via bootstrap classloader]"; //$NON-NLS-1$
993-
}
994-
return cl.getName();
995-
}
996-
997960
@Override
998961
public void setDefaultSupplier(PrimaryObjectSupplier objectSupplier) {
999962
defaultSupplier = objectSupplier;

0 commit comments

Comments
 (0)