Skip to content

Commit 3811491

Browse files
committed
Support multiple versions of annotation classes in E4 Injector
and specifically support jakarta.annotation version 3.0. Fixes #1565
1 parent 44074c7 commit 3811491

File tree

4 files changed

+99
-171
lines changed

4 files changed

+99
-171
lines changed

runtime/bundles/org.eclipse.e4.core.di/META-INF/MANIFEST.MF

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ Export-Package: org.eclipse.e4.core.di;version="1.7.0",
1717
Require-Bundle: org.eclipse.e4.core.di.annotations;bundle-version="[1.4.0,2.0.0)";visibility:=reexport
1818
Import-Package: jakarta.annotation;version="[2,3)",
1919
jakarta.inject;version="[2,3)",
20-
javax.annotation;version="[1.3.0,2.0.0)";resolution:=optional,
2120
javax.inject;version="[1.0.0,2.0.0)";resolution:=optional,
2221
org.eclipse.osgi.framework.log;version="1.1.0",
2322
org.osgi.framework;version="[1.8.0,2.0.0)",

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

Lines changed: 96 additions & 87 deletions
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,130 @@ 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);
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$
6673

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$
7178

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$
7380
null);
7481

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));
8185
}
8286

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$
8589

8690
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());
9392
}
9493

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<>();
9995

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);
115119
}
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);
123126
}
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);
129127
}
130128

131129
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+
}
138137
}
139138
}
140139
return null;
141140
}
142141

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+
});
151157
}
152158

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);
160167
}
161168

162169
private static boolean javaxWarningPrinted = false;
163170

164-
private static void loadJavaxClass(Runnable run) {
171+
private static boolean canLoadJavaxClass(String className) {
165172
try {
166173
if (!getSystemPropertyFlag("eclipse.e4.inject.javax.disabled", false)) { //$NON-NLS-1$
167-
run.run();
174+
Class.forName(className); // fails if absent
168175
if (!javaxWarningPrinted) {
169176
if (getSystemPropertyFlag("eclipse.e4.inject.javax.warning", true)) { //$NON-NLS-1$
170177
@SuppressWarnings("nls")
@@ -179,10 +186,12 @@ private static void loadJavaxClass(Runnable run) {
179186
}
180187
javaxWarningPrinted = true;
181188
}
189+
return true;
182190
}
183-
} catch (NoClassDefFoundError e) {
191+
} catch (NoClassDefFoundError | ClassNotFoundException e) {
184192
// Ignore exception: javax-annotation seems to be unavailable in the runtime
185193
}
194+
return false;
186195
}
187196

188197
private static boolean getSystemPropertyFlag(String key, boolean defaultValue) {

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

Lines changed: 3 additions & 40 deletions
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.
@@ -838,13 +834,13 @@ private Class<?> getDesiredClass(Type desiredType) {
838834
* Returns null if not a provider
839835
*/
840836
private Class<?> getProviderType(Type type) {
841-
if (!(type instanceof ParameterizedType))
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<?>))
@@ -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)