Skip to content

Commit c806698

Browse files
committed
Support multiple versions of annotation classes in E4 Injector
and specifically support jakarta.annotation version 3.0. This also removes the warning about javax.inject/javax.annotation being present in the application and the associated deprecation warning. Supporting the javax-annotations is now just a matter of listing their names as Strings and the actual classes don't have to be provided anymore. Therefore they can be supported probably forever. Fixes #1565
1 parent 7c138cf commit c806698

5 files changed

Lines changed: 86 additions & 235 deletions

File tree

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +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.0.0,4.0.0)",
1919
jakarta.inject;version="[2,3)",
20-
javax.annotation;version="[1.3.0,2.0.0)";resolution:=optional,
21-
javax.inject;version="[1.0.0,2.0.0)";resolution:=optional,
2220
org.eclipse.osgi.framework.log;version="1.1.0",
2321
org.osgi.framework;version="[1.8.0,2.0.0)",
2422
org.osgi.util.tracker;version="[1.5.1,2.0.0)"
Lines changed: 81 additions & 125 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, 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
@@ -10,21 +10,25 @@
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.MethodHandle;
20+
import java.lang.invoke.MethodHandles;
21+
import java.lang.invoke.MethodHandles.Lookup;
22+
import java.lang.invoke.MethodType;
1823
import java.lang.reflect.AnnotatedElement;
24+
import java.lang.reflect.ParameterizedType;
25+
import java.lang.reflect.Proxy;
1926
import java.lang.reflect.Type;
20-
import java.util.ArrayList;
21-
import java.util.HashMap;
22-
import java.util.List;
27+
import java.util.Collections;
2328
import java.util.Map;
24-
import java.util.Map.Entry;
29+
import java.util.Set;
30+
import java.util.WeakHashMap;
2531
import java.util.function.Function;
26-
import java.util.function.Supplier;
27-
import org.eclipse.e4.core.di.IInjector;
2832
import org.eclipse.e4.core.di.suppliers.IObjectDescriptor;
2933
import 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
}

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

Lines changed: 5 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*******************************************************************************
2-
* Copyright (c) 2009, 2023 IBM Corporation and others.
2+
* Copyright (c) 2009, 2026 IBM Corporation and others.
33
*
44
* This program and the accompanying materials
55
* are made available under the terms of the Eclipse Public License 2.0
@@ -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.
@@ -338,7 +334,7 @@ public Object makeFromProvider(IObjectDescriptor descriptor, PrimaryObjectSuppli
338334
Binding binding = findBinding(descriptor);
339335
Class<?> implementationClass;
340336
if (binding == null) {
341-
implementationClass = getProviderType(descriptor.getDesiredType());
337+
implementationClass = getProvidedType(descriptor.getDesiredType());
342338
} else {
343339
implementationClass = binding.getImplementationClass();
344340
}
@@ -505,7 +501,7 @@ private Object[] resolveArgs(Requestor<?> requestor, PrimaryObjectSupplier objec
505501

506502
// 1) check if we have a Provider<T>
507503
for (int i = 0; i < actualArgs.length; i++) {
508-
Class<?> providerClass = getProviderType(descriptors[i].getDesiredType());
504+
Class<?> providerClass = getProvidedType(descriptors[i].getDesiredType());
509505
if (providerClass == null) {
510506
continue;
511507
}
@@ -887,7 +883,7 @@ private Class<?> getDesiredClass(Type desiredType) {
887883
/**
888884
* Returns null if not a provider
889885
*/
890-
private Class<?> getProviderType(Type type) {
886+
private Class<?> getProvidedType(Type type) {
891887
if (!(type instanceof ParameterizedType)) {
892888
return null;
893889
}
@@ -936,7 +932,7 @@ public IBinding addBinding(IBinding binding) {
936932
}
937933

938934
private Binding findBinding(IObjectDescriptor descriptor) {
939-
Class<?> desiredClass = getProviderType(descriptor.getDesiredType());
935+
Class<?> desiredClass = getProvidedType(descriptor.getDesiredType());
940936
if (desiredClass == null) {
941937
desiredClass = getDesiredClass(descriptor.getDesiredType());
942938
}
@@ -1001,23 +997,6 @@ private void processAnnotated(AnnotationProxy annotation, Object userObject, Cla
1001997
Method[] methods = getDeclaredMethods(objectClass);
1002998
for (Method method : methods) {
1003999
if (!isAnnotationPresent(method, annotation)) {
1004-
if (shouldDebug) {
1005-
for (Annotation a : method.getAnnotations()) {
1006-
if (annotation.classes().stream().map(Class::getName)
1007-
.anyMatch(a.annotationType().getName()::equals)) {
1008-
StringBuilder tmp = new StringBuilder();
1009-
tmp.append("Possbible annotation mismatch: method \""); //$NON-NLS-1$
1010-
tmp.append(method.toString());
1011-
tmp.append("\" annotated with \""); //$NON-NLS-1$
1012-
tmp.append(describeClass(a.annotationType()));
1013-
tmp.append("\" but was looking for \""); //$NON-NLS-1$
1014-
tmp.append(annotation.classes().stream().map(InjectorImpl::describeClass)
1015-
.collect(Collectors.joining(System.lineSeparator() + " or "))); //$NON-NLS-1$
1016-
tmp.append("\""); //$NON-NLS-1$
1017-
LogHelper.logWarning(tmp.toString(), null);
1018-
}
1019-
}
1020-
}
10211000
continue;
10221001
}
10231002
if (isOverridden(method, classHierarchy)) {
@@ -1038,22 +1017,6 @@ private void processAnnotated(AnnotationProxy annotation, Object userObject, Cla
10381017
}
10391018
}
10401019

1041-
/** Provide a human-meaningful description of the provided class */
1042-
private static String describeClass(Class<?> cl) {
1043-
Bundle b = FrameworkUtil.getBundle(cl);
1044-
if (b != null) {
1045-
return b.getSymbolicName() + ":" + b.getVersion() + ":" + cl.getName(); //$NON-NLS-1$ //$NON-NLS-2$
1046-
}
1047-
CodeSource clazzCS = cl.getProtectionDomain().getCodeSource();
1048-
if (clazzCS != null) {
1049-
return clazzCS.getLocation() + ">" + cl.getName(); //$NON-NLS-1$
1050-
}
1051-
if (cl.getClassLoader() == null) {
1052-
return cl.getName() + " [via bootstrap classloader]"; //$NON-NLS-1$
1053-
}
1054-
return cl.getName();
1055-
}
1056-
10571020
@Override
10581021
public void setDefaultSupplier(PrimaryObjectSupplier objectSupplier) {
10591022
defaultSupplier = objectSupplier;

0 commit comments

Comments
 (0)