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..f147a8837f4 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 @@ -17,7 +17,6 @@ Export-Package: org.eclipse.e4.core.di;version="1.7.0", 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, 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/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 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 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 genericProvider = () -> ((InjectorImpl) injector).makeFromProvider(descriptor, provider); + return factory.bindTo(genericProvider).invoke(); + } 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, Function> entry : annotations) { - Class 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 annotationType = namedAnnotation.annotationType(); + if (NAMED_ANNOTATION_CLASSES.contains(annotationType.getName())) { + return namedAnnotationValueGetter(annotationType).apply(namedAnnotation); + } } } return null; } - private static final Map, Function> NAMED_ANNOTATION2VALUE_GETTER; - - static { - Map, Function> 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 namedAnnotationValueGetter( + Class annotationType) { + return NAMED_ANNOTATION2VALUE_GETTER2.computeIfAbsent(annotationType, type -> { + try { + 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) site.getTarget().invokeExact(); + } catch (Throwable e) { + throw new IllegalStateException(e); + } + }); } - private static List> getAvailableClasses(Class jakartaClass, Supplier> javaxClass) { - List> classes = new ArrayList<>(); - classes.add(jakartaClass); - if (javaxClass != null) { - loadJavaxClass(() -> classes.add(javaxClass.get())); - } - return classes; + private static final Map, Function> NAMED_ANNOTATION2VALUE_GETTER2 = new ConcurrentHashMap<>(); + private static final Set 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 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 +186,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..8db9cc43433 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. @@ -838,13 +834,13 @@ private Class getDesiredClass(Type desiredType) { * Returns null if not a provider */ private Class getProviderType(Type type) { - if (!(type instanceof ParameterizedType)) + 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)) @@ -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 implements Provider { - - 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; - } - } - -}