From 89361645239064b0b8cc25f62ffd532271d9994e Mon Sep 17 00:00:00 2001
From: Hannes Wellmann
Date: Mon, 23 Sep 2024 19:19:08 +0200
Subject: [PATCH] Support multiple versions of annotation classes in E4
Injector
and specifically support jakarta.annotation version 3.0.
Fixes https://github.com/eclipse-platform/eclipse.platform/issues/1565
---
.../META-INF/MANIFEST.MF | 4 +-
.../src/org/eclipse/e4/core/di/IInjector.java | 7 +-
.../e4/core/internal/di/AnnotationLookup.java | 183 +++++++++---------
.../e4/core/internal/di/InjectorImpl.java | 43 +---
.../e4/core/internal/di/ProviderImpl.java | 43 ----
5 files changed, 103 insertions(+), 177 deletions(-)
delete mode 100644 runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/ProviderImpl.java
diff --git a/runtime/bundles/org.eclipse.e4.core.di/META-INF/MANIFEST.MF b/runtime/bundles/org.eclipse.e4.core.di/META-INF/MANIFEST.MF
index a084435dfde..4ffc603aef1 100644
--- a/runtime/bundles/org.eclipse.e4.core.di/META-INF/MANIFEST.MF
+++ b/runtime/bundles/org.eclipse.e4.core.di/META-INF/MANIFEST.MF
@@ -15,9 +15,7 @@ Export-Package: org.eclipse.e4.core.di;version="1.7.0",
org.eclipse.e4.core.internal.di.osgi;x-internal:=true,
org.eclipse.e4.core.internal.di.shared;x-friends:="org.eclipse.e4.core.contexts,org.eclipse.e4.core.di.extensions.supplier"
Require-Bundle: org.eclipse.e4.core.di.annotations;bundle-version="[1.4.0,2.0.0)";visibility:=reexport
-Import-Package: jakarta.annotation;version="[2,3)",
- jakarta.inject;version="[2,3)",
- javax.annotation;version="[1.3.0,2.0.0)";resolution:=optional,
+Import-Package: jakarta.inject;version="[2,3)",
javax.inject;version="[1.0.0,2.0.0)";resolution:=optional,
org.eclipse.osgi.framework.log;version="1.1.0",
org.osgi.framework;version="[1.8.0,2.0.0)",
diff --git a/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/di/IInjector.java b/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/di/IInjector.java
index ee96a8ed073..0d38b391a6a 100644
--- a/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/di/IInjector.java
+++ b/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/di/IInjector.java
@@ -14,8 +14,6 @@
*******************************************************************************/
package org.eclipse.e4.core.di;
-import jakarta.annotation.PostConstruct;
-import jakarta.annotation.PreDestroy;
import jakarta.inject.Scope;
import jakarta.inject.Singleton;
import java.lang.annotation.Annotation;
@@ -37,7 +35,7 @@
* The injection of values is generally done as a number of calls. User objects
* that want to finalize the injected data (for instance, to perform
* calculations based on multiple injected values) can place such calculations
- * in a method with the {@link PostConstruct} annotation.
+ * in a method with the {@link jakarta.annotation.PostConstruct} annotation.
*
*
* When injecting values, all fields are injected prior to injection of methods.
@@ -51,7 +49,8 @@
*
*
* When supplier is disposed, the injector will attempt to notify all injected
- * objects by calling methods with the {@link PreDestroy} annotation.
+ * objects by calling methods with the {@link jakarta.annotation.PreDestroy}
+ * annotation.
*
* @noimplement This interface is not intended to be implemented by clients.
* @noextend This interface is not intended to be extended by clients.
diff --git a/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/AnnotationLookup.java b/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/AnnotationLookup.java
index d6793c24280..8e860c6abd0 100644
--- a/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/AnnotationLookup.java
+++ b/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/AnnotationLookup.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2023, 2023 Hannes Wellmann and others.
+ * Copyright (c) 2023, 2024 Hannes Wellmann and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -10,18 +10,24 @@
*
* Contributors:
* Hannes Wellmann - initial API and implementation
+ * Hannes Wellmann - support multiple versions of one annotation class
*******************************************************************************/
package org.eclipse.e4.core.internal.di;
import java.lang.annotation.Annotation;
+import java.lang.invoke.CallSite;
+import java.lang.invoke.LambdaMetafactory;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
-import java.util.ArrayList;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Supplier;
import org.eclipse.e4.core.di.IInjector;
@@ -42,14 +48,15 @@ public class AnnotationLookup {
private AnnotationLookup() {
}
- public static record AnnotationProxy(List> classes) {
+ public static record AnnotationProxy(List classes) {
+
public AnnotationProxy {
classes = List.copyOf(classes);
}
public boolean isPresent(AnnotatedElement element) {
- for (Class extends Annotation> annotationClass : classes) {
- if (element.isAnnotationPresent(annotationClass)) {
+ for (Annotation annotation : element.getAnnotations()) {
+ if (classes.contains(annotation.annotationType().getName())) {
return true;
}
}
@@ -57,114 +64,114 @@ public boolean isPresent(AnnotatedElement element) {
}
}
- static final AnnotationProxy INJECT = createProxyForClasses(jakarta.inject.Inject.class,
- () -> javax.inject.Inject.class);
- static final AnnotationProxy SINGLETON = createProxyForClasses(jakarta.inject.Singleton.class,
- () -> javax.inject.Singleton.class);
- static final AnnotationProxy QUALIFIER = createProxyForClasses(jakarta.inject.Qualifier.class,
- () -> javax.inject.Qualifier.class);
+ static final AnnotationProxy INJECT = createProxyForClasses("jakarta.inject.Inject", //$NON-NLS-1$
+ "javax.inject.Inject"); //$NON-NLS-1$
+ static final AnnotationProxy SINGLETON = createProxyForClasses("jakarta.inject.Singleton", //$NON-NLS-1$
+ "javax.inject.Singleton"); //$NON-NLS-1$
+ static final AnnotationProxy QUALIFIER = createProxyForClasses("jakarta.inject.Qualifier", //$NON-NLS-1$
+ "javax.inject.Qualifier"); //$NON-NLS-1$
- static final AnnotationProxy PRE_DESTROY = createProxyForClasses(jakarta.annotation.PreDestroy.class,
- () -> javax.annotation.PreDestroy.class);
- public static final AnnotationProxy POST_CONSTRUCT = createProxyForClasses(jakarta.annotation.PostConstruct.class,
- () -> javax.annotation.PostConstruct.class);
+ static final AnnotationProxy PRE_DESTROY = createProxyForClasses("jakarta.annotation.PreDestroy", //$NON-NLS-1$
+ "javax.annotation.PreDestroy"); //$NON-NLS-1$
+ public static final AnnotationProxy POST_CONSTRUCT = createProxyForClasses("jakarta.annotation.PostConstruct", //$NON-NLS-1$
+ "javax.annotation.PostConstruct"); //$NON-NLS-1$
- static final AnnotationProxy OPTIONAL = createProxyForClasses(org.eclipse.e4.core.di.annotations.Optional.class,
+ static final AnnotationProxy OPTIONAL = createProxyForClasses("org.eclipse.e4.core.di.annotations.Optional", //$NON-NLS-1$
null);
- private static AnnotationProxy createProxyForClasses(Class extends Annotation> jakartaAnnotationClass,
- Supplier> javaxAnnotationClass) {
- List> classes = getAvailableClasses(jakartaAnnotationClass, javaxAnnotationClass);
- @SuppressWarnings({ "rawtypes", "unchecked" })
- List> annotationClasses = (List) classes;
- return new AnnotationProxy(annotationClasses);
+ private static AnnotationProxy createProxyForClasses(String jakartaAnnotationClass,
+ String javaxAnnotationClass) {
+ return new AnnotationProxy(getAvailableClasses(jakartaAnnotationClass, javaxAnnotationClass));
}
- private static final List> PROVIDER_TYPES = getAvailableClasses(jakarta.inject.Provider.class,
- () -> javax.inject.Provider.class);
+ private static final Set PROVIDER_TYPES = Set
+ .copyOf(getAvailableClasses("jakarta.inject.Provider", "javax.inject.Provider")); //$NON-NLS-1$//$NON-NLS-2$
static boolean isProvider(Type type) {
- for (Class> clazz : PROVIDER_TYPES) {
- if (clazz.equals(type)) {
- return true;
- }
- }
- return false;
+ return PROVIDER_TYPES.contains(type.getTypeName());
}
- @FunctionalInterface
- private interface ProviderFactory {
- Object create(IObjectDescriptor descriptor, IInjector injector, PrimaryObjectSupplier provider);
- }
+ private static final Map, MethodHandle> PROVIDER_FACTORYS = new ConcurrentHashMap<>();
- private static final ProviderFactory PROVIDER_FACTORY;
- static {
- ProviderFactory factory;
- try {
- /**
- * This subclass solely exists for the purpose to not require the presence of
- * the javax.inject.Provider interface in the runtime when the base-class is
- * loaded. This can be deleted when support for javax is removed form the
- * E4-injector.
- */
- class JavaxCompatibilityProviderImpl extends ProviderImpl implements javax.inject.Provider {
- public JavaxCompatibilityProviderImpl(IObjectDescriptor descriptor, IInjector injector,
- PrimaryObjectSupplier provider) {
- super(descriptor, injector, provider);
- }
+ public static Object getProvider(IObjectDescriptor descriptor, IInjector injector, PrimaryObjectSupplier provider) {
+ Class> providerClass;
+ Type desiredType = descriptor.getDesiredType();
+ if ((desiredType instanceof ParameterizedType parameterizedType
+ && parameterizedType.getRawType() instanceof Class> clazz)) {
+ providerClass = clazz;
+ } else if (desiredType instanceof Class> clazz) {
+ providerClass = clazz;
+ } else {
+ throw new IllegalArgumentException();
+ }
+ // Dynamically create a lambda implementing the providerClass
+ MethodHandle factory = PROVIDER_FACTORYS.computeIfAbsent(providerClass, providerType -> {
+ try {
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
+ MethodType suppliedType = MethodType.methodType(Object.class);
+ CallSite callSite = LambdaMetafactory.metafactory(lookup, "get", //$NON-NLS-1$
+ MethodType.methodType(providerClass, Supplier.class), suppliedType.erase(), //
+ lookup.findVirtual(Supplier.class, "get", suppliedType), //$NON-NLS-1$
+ suppliedType);
+ return callSite.getTarget();
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
}
- factory = JavaxCompatibilityProviderImpl::new;
- // Attempt to load the class early in order to enforce an early class-loading
- // and to be able to handle the NoClassDefFoundError below in case
- // javax-Provider is not available in the runtime:
- factory.create(null, null, null);
- } catch (NoClassDefFoundError e) {
- factory = ProviderImpl::new;
+ });
+ try {
+ Supplier