From ad44f11f875fe47923fc3e0fce23f81725a383b7 Mon Sep 17 00:00:00 2001
From: Hannes Wellmann <wellmann.hannes1@gmx.net>
Date: Mon, 23 Sep 2024 19:19:08 +0200
Subject: [PATCH 1/2] 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
---
 .../e4/core/internal/di/AnnotationLookup.java | 196 ++++++++++--------
 .../e4/core/internal/di/InjectorImpl.java     |  51 +----
 .../e4/core/internal/di/ProviderImpl.java     |  43 ----
 3 files changed, 112 insertions(+), 178 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/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..07f3954a855 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<Class<? extends Annotation>> classes) {
+	public static record AnnotationProxy(List<String> 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,119 @@ 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 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 OPTIONAL = createProxyForClasses(org.eclipse.e4.core.di.annotations.Optional.class,
-			null);
-
-	private static AnnotationProxy createProxyForClasses(Class<? extends Annotation> jakartaAnnotationClass,
-			Supplier<Class<? extends Annotation>> javaxAnnotationClass) {
-		List<Class<?>> classes = getAvailableClasses(jakartaAnnotationClass, javaxAnnotationClass);
-		@SuppressWarnings({ "rawtypes", "unchecked" })
-		List<Class<? extends Annotation>> annotationClasses = (List) classes;
-		return new AnnotationProxy(annotationClasses);
+	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", //$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", null); //$NON-NLS-1$
+
+	private static AnnotationProxy createProxyForClasses(String jakartaAnnotationClass,
+			String javaxAnnotationClass) {
+		return new AnnotationProxy(getAvailableClasses(jakartaAnnotationClass, javaxAnnotationClass));
 	}
 
-	private static final List<Class<?>> PROVIDER_TYPES = getAvailableClasses(jakarta.inject.Provider.class,
-			() -> javax.inject.Provider.class);
+	private static final Set<String> 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<Class<?>, 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<T> extends ProviderImpl<T> implements javax.inject.Provider<T> {
-				public JavaxCompatibilityProviderImpl(IObjectDescriptor descriptor, IInjector injector,
-						PrimaryObjectSupplier provider) {
-					super(descriptor, injector, provider);
-				}
+	public static Object getProvider(IObjectDescriptor descriptor, IInjector injector, PrimaryObjectSupplier provider) {
+
+		Supplier<Object> genericProvider = () -> ((InjectorImpl) injector).makeFromProvider(descriptor, provider);
+
+		Class<?> providerClass;
+		if ((descriptor.getDesiredType() instanceof ParameterizedType parameterizedType
+				&& parameterizedType.getRawType() instanceof Class<?> clazz)) {
+			providerClass = clazz;
+		} else {
+			throw new IllegalStateException(); // The caller must ensure the providerClass can be extracted
+		}
+		// At runtime dynamically create a method-reference that implements the specific
+		// providerClass 'foo.bar.Provider':
+		// (foo.bar.Provider) genericProvider::get
+		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", MethodType.methodType(Object.class)), //$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 {
+			Object providerImpl = factory.bindTo(genericProvider).invoke();
+			return providerClass.cast(providerImpl);
+		} catch (Throwable e) {
+			throw new IllegalStateException(e);
 		}
-		PROVIDER_FACTORY = factory;
-	}
-
-	public static Object getProvider(IObjectDescriptor descriptor, IInjector injector, PrimaryObjectSupplier provider) {
-		return PROVIDER_FACTORY.create(descriptor, injector, provider);
 	}
 
 	public static String getQualifierValue(IObjectDescriptor descriptor) {
-		var annotations = NAMED_ANNOTATION2VALUE_GETTER.entrySet();
-		for (Entry<Class<? extends Annotation>, Function<Annotation, String>> entry : annotations) {
-			Class<? extends Annotation> annotationClass = entry.getKey();
-			if (descriptor.hasQualifier(annotationClass)) {
-				Annotation namedAnnotation = descriptor.getQualifier(annotationClass);
-				return entry.getValue().apply(namedAnnotation);
+		Annotation[] qualifiers = descriptor.getQualifiers();
+		if (qualifiers != null) {
+			for (Annotation namedAnnotation : qualifiers) {
+				Class<? extends Annotation> annotationType = namedAnnotation.annotationType();
+				if (NAMED_ANNOTATION_CLASSES.contains(annotationType.getName())) {
+					return namedAnnotationValueGetter(annotationType).apply(namedAnnotation);
+				}
 			}
 		}
 		return null;
 	}
 
-	private static final Map<Class<? extends Annotation>, Function<Annotation, String>> NAMED_ANNOTATION2VALUE_GETTER;
-
-	static {
-		Map<Class<? extends Annotation>, Function<Annotation, String>> annotation2valueGetter = new HashMap<>();
-		annotation2valueGetter.put(jakarta.inject.Named.class, a -> ((jakarta.inject.Named) a).value());
-		loadJavaxClass(
-				() -> annotation2valueGetter.put(javax.inject.Named.class, a -> ((javax.inject.Named) a).value()));
-		NAMED_ANNOTATION2VALUE_GETTER = Map.copyOf(annotation2valueGetter);
+	private static Function<Annotation, String> namedAnnotationValueGetter(
+			Class<? extends Annotation> annotationType) {
+		return NAMED_ANNOTATION2VALUE_GETTER2.computeIfAbsent(annotationType, type -> {
+			try {
+				// At runtime dynamically create the method-reference: 'foo.bar.Named::value'
+				// where 'foo.bar.Named' is the passed specific annotationType. Invoking the
+				// returned Function built from the method reference is much faster than using
+				// reflection.
+				MethodHandles.Lookup lookup = MethodHandles.lookup();
+				MethodType functionApplySignature = MethodType.methodType(String.class, type);
+				CallSite site = LambdaMetafactory.metafactory(lookup, "apply", //$NON-NLS-1$
+						MethodType.methodType(Function.class), functionApplySignature.erase(),
+						lookup.findVirtual(type, "value", MethodType.methodType(String.class)), //$NON-NLS-1$
+						functionApplySignature);
+				return (Function<Annotation, String>) site.getTarget().invokeExact();
+			} catch (Throwable e) {
+				throw new IllegalStateException(e);
+			}
+		});
 	}
 
-	private static List<Class<?>> getAvailableClasses(Class<?> jakartaClass, Supplier<? extends Class<?>> javaxClass) {
-		List<Class<?>> classes = new ArrayList<>();
-		classes.add(jakartaClass);
-		if (javaxClass != null) {
-			loadJavaxClass(() -> classes.add(javaxClass.get()));
-		}
-		return classes;
+	private static final Map<Class<? extends Annotation>, Function<Annotation, String>> NAMED_ANNOTATION2VALUE_GETTER2 = new ConcurrentHashMap<>();
+	private static final Set<String> NAMED_ANNOTATION_CLASSES = Set.of("jakarta.inject.Named", "javax.inject.Named"); //$NON-NLS-1$//$NON-NLS-2$
+	// TODO: warn about the javax-class?
+
+	private static List<String> getAvailableClasses(String jakartaClass, String javaxClass) {
+		return javaxClass != null && canLoadJavaxClass(javaxClass) //
+				? List.of(jakartaClass, javaxClass)
+				: List.of(jakartaClass);
 	}
 
 	private static boolean javaxWarningPrinted = false;
 
-	private static void loadJavaxClass(Runnable run) {
+	private static boolean canLoadJavaxClass(String className) {
 		try {
 			if (!getSystemPropertyFlag("eclipse.e4.inject.javax.disabled", false)) { //$NON-NLS-1$
-				run.run();
+				Class.forName(className); // fails if absent
 				if (!javaxWarningPrinted) {
 					if (getSystemPropertyFlag("eclipse.e4.inject.javax.warning", true)) { //$NON-NLS-1$
 						@SuppressWarnings("nls")
@@ -179,10 +191,12 @@ private static void loadJavaxClass(Runnable run) {
 					}
 					javaxWarningPrinted = true;
 				}
+				return true;
 			}
-		} catch (NoClassDefFoundError e) {
+		} catch (NoClassDefFoundError | ClassNotFoundException e) {
 			// Ignore exception: javax-annotation seems to be unavailable in the runtime
 		}
+		return false;
 	}
 
 	private static boolean getSystemPropertyFlag(String key, boolean defaultValue) {
diff --git a/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/InjectorImpl.java b/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/InjectorImpl.java
index 9ea475e1ee6..b56c5937549 100644
--- a/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/InjectorImpl.java
+++ b/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/InjectorImpl.java
@@ -24,7 +24,6 @@
 import java.lang.reflect.Modifier;
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
-import java.security.CodeSource;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -37,7 +36,6 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.WeakHashMap;
-import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.eclipse.e4.core.di.IBinding;
 import org.eclipse.e4.core.di.IInjector;
@@ -50,8 +48,6 @@
 import org.eclipse.e4.core.di.suppliers.PrimaryObjectSupplier;
 import org.eclipse.e4.core.internal.di.AnnotationLookup.AnnotationProxy;
 import org.eclipse.e4.core.internal.di.osgi.LogHelper;
-import org.osgi.framework.Bundle;
-import org.osgi.framework.FrameworkUtil;
 
 /**
  * Reflection-based dependency injector.
@@ -330,7 +326,7 @@ public Object makeFromProvider(IObjectDescriptor descriptor, PrimaryObjectSuppli
 		Binding binding = findBinding(descriptor);
 		Class<?> implementationClass;
 		if (binding == null)
-			implementationClass = getProviderType(descriptor.getDesiredType());
+			implementationClass = getProvidedType(descriptor.getDesiredType());
 		else
 			implementationClass = binding.getImplementationClass();
 		if (objectSupplier != null) {
@@ -486,7 +482,7 @@ private Object[] resolveArgs(Requestor<?> requestor, PrimaryObjectSupplier objec
 
 		// 1) check if we have a Provider<T>
 		for (int i = 0; i < actualArgs.length; i++) {
-			Class<?> providerClass = getProviderType(descriptors[i].getDesiredType());
+			Class<?> providerClass = getProvidedType(descriptors[i].getDesiredType());
 			if (providerClass == null) {
 				continue;
 			}
@@ -837,14 +833,14 @@ private Class<?> getDesiredClass(Type desiredType) {
 	/**
 	 * Returns null if not a provider
 	 */
-	private Class<?> getProviderType(Type type) {
-		if (!(type instanceof ParameterizedType))
+	private Class<?> getProvidedType(Type type) {
+		if (!(type instanceof ParameterizedType parameterizedType))
 			return null;
-		Type rawType = ((ParameterizedType) type).getRawType();
+		Type rawType = parameterizedType.getRawType();
 		if (!AnnotationLookup.isProvider(rawType)) {
 			return null;
 		}
-		Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments();
+		Type[] actualTypes = parameterizedType.getActualTypeArguments();
 		if (actualTypes.length != 1)
 			return null;
 		if (!(actualTypes[0] instanceof Class<?>))
@@ -883,7 +879,7 @@ public IBinding addBinding(IBinding binding) {
 	}
 
 	private Binding findBinding(IObjectDescriptor descriptor) {
-		Class<?> desiredClass = getProviderType(descriptor.getDesiredType());
+		Class<?> desiredClass = getProvidedType(descriptor.getDesiredType());
 		if (desiredClass == null)
 			desiredClass = getDesiredClass(descriptor.getDesiredType());
 		synchronized (bindings) {
@@ -942,23 +938,6 @@ private void processAnnotated(AnnotationProxy annotation, Object userObject, Cla
 		Method[] methods = getDeclaredMethods(objectClass);
 		for (Method method : methods) {
 			if (!isAnnotationPresent(method, annotation)) {
-				if (shouldDebug) {
-					for (Annotation a : method.getAnnotations()) {
-						if (annotation.classes().stream().map(Class::getName)
-								.anyMatch(a.annotationType().getName()::equals)) {
-							StringBuilder tmp = new StringBuilder();
-							tmp.append("Possbible annotation mismatch: method \""); //$NON-NLS-1$
-							tmp.append(method.toString());
-							tmp.append("\" annotated with \""); //$NON-NLS-1$
-							tmp.append(describeClass(a.annotationType()));
-							tmp.append("\" but was looking for \""); //$NON-NLS-1$
-							tmp.append(annotation.classes().stream().map(InjectorImpl::describeClass)
-									.collect(Collectors.joining(System.lineSeparator() + " or "))); //$NON-NLS-1$
-							tmp.append("\""); //$NON-NLS-1$
-							LogHelper.logWarning(tmp.toString(), null);
-						}
-					}
-				}
 				continue;
 			}
 			if (isOverridden(method, classHierarchy))
@@ -978,22 +957,6 @@ private void processAnnotated(AnnotationProxy annotation, Object userObject, Cla
 		}
 	}
 
-	/** Provide a human-meaningful description of the provided class */
-	private static String describeClass(Class<?> cl) {
-		Bundle b = FrameworkUtil.getBundle(cl);
-		if (b != null) {
-			return b.getSymbolicName() + ":" + b.getVersion() + ":" + cl.getName(); //$NON-NLS-1$ //$NON-NLS-2$
-		}
-		CodeSource clazzCS = cl.getProtectionDomain().getCodeSource();
-		if (clazzCS != null) {
-			return clazzCS.getLocation() + ">" + cl.getName(); //$NON-NLS-1$
-		}
-		if (cl.getClassLoader() == null) {
-			return cl.getName() + " [via bootstrap classloader]"; //$NON-NLS-1$
-		}
-		return cl.getName();
-	}
-
 	@Override
 	public void setDefaultSupplier(PrimaryObjectSupplier objectSupplier) {
 		defaultSupplier = objectSupplier;
diff --git a/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/ProviderImpl.java b/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/ProviderImpl.java
deleted file mode 100644
index d137cec1c7e..00000000000
--- a/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/ProviderImpl.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2009, 2015 IBM Corporation and others.
- *
- * This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License 2.0
- * which accompanies this distribution, and is available at
- * https://www.eclipse.org/legal/epl-2.0/
- *
- * SPDX-License-Identifier: EPL-2.0
- *
- * Contributors:
- *     IBM Corporation - initial API and implementation
- *******************************************************************************/
-package org.eclipse.e4.core.internal.di;
-
-import jakarta.inject.Provider;
-import org.eclipse.e4.core.di.IInjector;
-import org.eclipse.e4.core.di.suppliers.IObjectDescriptor;
-import org.eclipse.e4.core.di.suppliers.PrimaryObjectSupplier;
-
-public class ProviderImpl<T> implements Provider<T> {
-
-	final private PrimaryObjectSupplier objectProvider;
-	final private IObjectDescriptor objectDescriptor;
-	final private IInjector injector;
-
-	public ProviderImpl(IObjectDescriptor descriptor, IInjector injector, PrimaryObjectSupplier provider) {
-		objectDescriptor = descriptor;
-		objectProvider = provider;
-		this.injector = injector;
-	}
-
-	@Override
-	@SuppressWarnings("unchecked")
-	public T get() {
-		try {
-			return (T) ((InjectorImpl) injector).makeFromProvider(objectDescriptor, objectProvider);
-		} catch (ClassCastException e) {
-			return null;
-		}
-	}
-
-}

From adc7232afc407dd19b436ee2158e8a75e9733174 Mon Sep 17 00:00:00 2001
From: Eclipse Platform Bot <platform-bot@eclipse.org>
Date: Fri, 6 Jun 2025 09:00:21 +0000
Subject: [PATCH 2/2] Version bump(s) for 4.37 stream

---
 runtime/bundles/org.eclipse.e4.core.di/META-INF/MANIFEST.MF | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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 29b2880e429..472aed925ba 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
@@ -1,7 +1,7 @@
 Manifest-Version: 1.0
 Bundle-ManifestVersion: 2
 Bundle-SymbolicName: org.eclipse.e4.core.di
-Bundle-Version: 1.9.600.qualifier
+Bundle-Version: 1.9.700.qualifier
 Bundle-Name: %pluginName
 Bundle-Vendor: %providerName
 Bundle-Localization: plugin