diff --git a/runtime/bundles/org.eclipse.e4.core.contexts/META-INF/MANIFEST.MF b/runtime/bundles/org.eclipse.e4.core.contexts/META-INF/MANIFEST.MF index 80aa2e6bc5b..1adb07d5902 100644 --- a/runtime/bundles/org.eclipse.e4.core.contexts/META-INF/MANIFEST.MF +++ b/runtime/bundles/org.eclipse.e4.core.contexts/META-INF/MANIFEST.MF @@ -9,7 +9,7 @@ Bundle-ActivationPolicy: lazy Require-Bundle: org.eclipse.e4.core.di Bundle-RequiredExecutionEnvironment: JavaSE-17 Import-Package: jakarta.inject;version="[2.0.0,3.0.0)", - javax.inject;version="[1.0.0,2.0.0)", + javax.inject;version="[1.0.0,2.0.0)";resolution:=optional, org.osgi.framework;version="[1.5.0,2.0.0)", org.osgi.service.event;version="[1.3.0,2.0.0)" Export-Package: org.eclipse.e4.core.contexts;version="1.7.0", diff --git a/runtime/bundles/org.eclipse.e4.core.contexts/src/org/eclipse/e4/core/contexts/ContextInjectionFactory.java b/runtime/bundles/org.eclipse.e4.core.contexts/src/org/eclipse/e4/core/contexts/ContextInjectionFactory.java index 60d79af3371..3e35c137198 100644 --- a/runtime/bundles/org.eclipse.e4.core.contexts/src/org/eclipse/e4/core/contexts/ContextInjectionFactory.java +++ b/runtime/bundles/org.eclipse.e4.core.contexts/src/org/eclipse/e4/core/contexts/ContextInjectionFactory.java @@ -34,13 +34,14 @@ *

* If annotations are supported by the runtime, matching of methods and fields * to be injected is also performed using the annotations defined in packages - * javax.inject and org.eclipse.e4.core.di.annotations. + * jakarta.inject and org.eclipse.e4.core.di.annotations. *

*

* 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 javax.annotation.PostConstruct annotation. + * in a method with the jakarta.annotation.PostConstruct + * annotation. *

*

* When injecting values, all fields are injected prior to injection of methods. @@ -55,7 +56,7 @@ *

* When a context is disposed, the injection factory will attempt to notify all * injected objects by calling methods with the - * javax.annotation.PreDestroy annotation. + * jakarta.annotation.PreDestroy annotation. * * This class is not intended to be extended by clients. * diff --git a/runtime/bundles/org.eclipse.e4.core.contexts/src/org/eclipse/e4/core/internal/contexts/ContextObjectSupplier.java b/runtime/bundles/org.eclipse.e4.core.contexts/src/org/eclipse/e4/core/internal/contexts/ContextObjectSupplier.java index c49528b6520..cfbf8409aab 100644 --- a/runtime/bundles/org.eclipse.e4.core.contexts/src/org/eclipse/e4/core/internal/contexts/ContextObjectSupplier.java +++ b/runtime/bundles/org.eclipse.e4.core.contexts/src/org/eclipse/e4/core/internal/contexts/ContextObjectSupplier.java @@ -26,6 +26,7 @@ import org.eclipse.e4.core.di.suppliers.IObjectDescriptor; import org.eclipse.e4.core.di.suppliers.IRequestor; import org.eclipse.e4.core.di.suppliers.PrimaryObjectSupplier; +import org.eclipse.e4.core.internal.di.AnnotationLookup; import org.eclipse.e4.core.internal.di.Requestor; public class ContextObjectSupplier extends PrimaryObjectSupplier { @@ -188,13 +189,9 @@ else if (targetContext.containsKey(keys[i])) } private String getKey(IObjectDescriptor descriptor) { - if (descriptor.hasQualifier(javax.inject.Named.class)) { - javax.inject.Named namedAnnotation = descriptor.getQualifier(javax.inject.Named.class); - return namedAnnotation.value(); - } - if (descriptor.hasQualifier(jakarta.inject.Named.class)) { - jakarta.inject.Named namedAnnotation = descriptor.getQualifier(jakarta.inject.Named.class); - return namedAnnotation.value(); + String value = AnnotationLookup.getQualifierValue(descriptor); + if (value != null) { + return value; } Type elementType = descriptor.getDesiredType(); return typeToString(elementType); diff --git a/runtime/bundles/org.eclipse.e4.core.di.annotations/META-INF/MANIFEST.MF b/runtime/bundles/org.eclipse.e4.core.di.annotations/META-INF/MANIFEST.MF index df1ef997d2f..7d106de6331 100644 --- a/runtime/bundles/org.eclipse.e4.core.di.annotations/META-INF/MANIFEST.MF +++ b/runtime/bundles/org.eclipse.e4.core.di.annotations/META-INF/MANIFEST.MF @@ -6,6 +6,6 @@ Bundle-Version: 1.8.200.qualifier Bundle-RequiredExecutionEnvironment: JavaSE-17 Export-Package: org.eclipse.e4.core.di.annotations;version="1.6.0" Import-Package: jakarta.inject;version="[2.0.0,3.0.0)", - javax.inject;version="[1.0.0,2.0.0)" + javax.inject;version="[1.0.0,2.0.0)";resolution:=optional Bundle-Vendor: %Bundle-Vendor Automatic-Module-Name: org.eclipse.e4.core.di.annotations diff --git a/runtime/bundles/org.eclipse.e4.core.di.extensions/META-INF/MANIFEST.MF b/runtime/bundles/org.eclipse.e4.core.di.extensions/META-INF/MANIFEST.MF index 53bd676b15e..b5fb4853dbf 100644 --- a/runtime/bundles/org.eclipse.e4.core.di.extensions/META-INF/MANIFEST.MF +++ b/runtime/bundles/org.eclipse.e4.core.di.extensions/META-INF/MANIFEST.MF @@ -9,5 +9,5 @@ Bundle-RequiredExecutionEnvironment: JavaSE-17 Export-Package: org.eclipse.e4.core.di.extensions;version="0.16.0" Bundle-Localization: fragment Import-Package: jakarta.inject;version="[2.0.0,3.0.0)", - javax.inject;version="[1.0.0,2.0.0)" + javax.inject;version="[1.0.0,2.0.0)";resolution:=optional Automatic-Module-Name: org.eclipse.e4.core.di.extensions 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 7c67322d83a..017bb7e81d8 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 @@ -13,10 +13,10 @@ 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: javax.annotation;version="[1.3.5,2.0.0)", - javax.inject;version="[1.0.0,2.0.0)", +Import-Package: jakarta.annotation;version="[2,3)", jakarta.inject;version="[2,3)", - jakarta.annotation;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)", org.osgi.util.tracker;version="[1.5.1,2.0.0)" diff --git a/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/di/suppliers/IObjectDescriptor.java b/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/di/suppliers/IObjectDescriptor.java index 5fcd714a752..e1ca06194dd 100644 --- a/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/di/suppliers/IObjectDescriptor.java +++ b/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/di/suppliers/IObjectDescriptor.java @@ -23,7 +23,7 @@ * set of optional qualifiers. *

* - * @see javax.inject.Qualifier + * @see jakarta.inject.Qualifier * @noextend This interface is not intended to be extended by clients. * @noimplement This interface is not intended to be implemented by clients. * @since 1.7 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 new file mode 100644 index 00000000000..767f6346dcd --- /dev/null +++ b/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/AnnotationLookup.java @@ -0,0 +1,185 @@ +/******************************************************************************* + * Copyright (c) 2023, 2023 Hannes Wellmann 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: + * Hannes Wellmann - initial API and implementation + *******************************************************************************/ + +package org.eclipse.e4.core.internal.di; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +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.function.Function; +import java.util.function.Supplier; +import org.eclipse.e4.core.di.IInjector; +import org.eclipse.e4.core.di.suppliers.IObjectDescriptor; +import org.eclipse.e4.core.di.suppliers.PrimaryObjectSupplier; + +/** + * A utility class to ease the look-up of jakarta/javax.inject and + * jakarta/javax.annotation annotations and types as mutual replacements, while + * being able to handle the absence of javax-classes in the runtime. + * + * If support for javax-annotations is removed, this class can be simplified to + * only handle jakarta-annotations, then all method can be inlined and this + * class eventually deleted, together with the entire test-project + * org.eclipse.e4.core.tests. + */ +public class AnnotationLookup { + private AnnotationLookup() { + } + + 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)) { + return true; + } + } + return false; + } + } + + 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 jakartaAnnotationClass, + Supplier> javaxAnnotationClass) { + List> classes = getAvailableClasses(jakartaAnnotationClass, javaxAnnotationClass); + @SuppressWarnings({ "rawtypes", "unchecked" }) + List> annotationClasses = (List) classes; + return new AnnotationProxy(annotationClasses); + } + + private static final List> PROVIDER_TYPES = getAvailableClasses(jakarta.inject.Provider.class, + () -> javax.inject.Provider.class); + + static boolean isProvider(Type type) { + for (Class clazz : PROVIDER_TYPES) { + if (clazz.equals(type)) { + return true; + } + } + return false; + } + + @FunctionalInterface + private interface ProviderFactory { + Object create(IObjectDescriptor descriptor, IInjector injector, PrimaryObjectSupplier provider); + } + + 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); + } + } + 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; + } + 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); + } + } + 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 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 boolean javaxWarningPrinted = false; + + private static void loadJavaxClass(Runnable run) { + try { + run.run(); + if (!javaxWarningPrinted) { + if (Boolean.parseBoolean(System.getProperty("eclipse.e4.inject.javax.warning", "true"))) { //$NON-NLS-1$//$NON-NLS-2$ + @SuppressWarnings("nls") + String message = """ + WARNING: Annotation classes from the 'javax.inject' or 'javax.annotation' package found. + It is recommended to migrate to the corresponding replacements in the jakarta namespace. + The Eclipse E4 Platform will remove support for those javax-annotations in a future release. + To suppress this warning set the VM property: -Declipse.e4.inject.javax.warning=false + """; + System.err.println(message); + } + javaxWarningPrinted = true; + } + } catch (NoClassDefFoundError e) { + // Ignore exception: javax-annotation seems to be unavailable in the runtime + } + } + +} 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 55fd0155d50..5b44a53c9d1 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 @@ -27,7 +27,6 @@ import java.security.CodeSource; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -38,6 +37,7 @@ 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; @@ -48,6 +48,7 @@ import org.eclipse.e4.core.di.suppliers.IObjectDescriptor; import org.eclipse.e4.core.di.suppliers.IRequestor; 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; @@ -72,9 +73,9 @@ public class InjectorImpl implements IInjector { private final Map>> injectedObjects = new WeakHashMap<>(); private final Set>> injectedClasses = new LinkedHashSet<>(); - private final HashMap, Object> singletonCache = new HashMap<>(); + private final Map, Object> singletonCache = new HashMap<>(); private final Map, Set> bindings = new HashMap<>(); - private final Map, Map> annotationsPresent = new HashMap<>(); + private final Map> annotationsPresent = new HashMap<>(); // Performance improvement: private final Map, Method[]> methodsCache = Collections.synchronizedMap(new WeakHashMap<>()); @@ -132,13 +133,10 @@ private void internalInject(Object object, PrimaryObjectSupplier objectSupplier, } rememberInjectedObject(object, objectSupplier); - // We call @javax.annotation.PostConstruct after injection. This means that is - // is called - // as a part of both #make() and #inject(). - processAnnotated(javax.annotation.PostConstruct.class, object, object.getClass(), objectSupplier, tempSupplier, + // We call @jakarta.annotation.PostConstruct after injection. This means that is + // is called as a part of both #make() and #inject(). + processAnnotated(AnnotationLookup.POST_CONSTRUCT, object, object.getClass(), objectSupplier, tempSupplier, new ArrayList<>(5)); - processAnnotated(jakarta.annotation.PostConstruct.class, object, object.getClass(), objectSupplier, - tempSupplier, new ArrayList<>(5)); // remove references to the temporary suppliers for (Requestor requestor : requestors) { @@ -180,12 +178,10 @@ public void uninject(Object object, PrimaryObjectSupplier objectSupplier) { try { if (!forgetInjectedObject(object, objectSupplier)) return; // not injected at this time - processAnnotated(javax.annotation.PreDestroy.class, object, object.getClass(), objectSupplier, null, - new ArrayList<>(5)); - processAnnotated(jakarta.annotation.PreDestroy.class, object, object.getClass(), objectSupplier, null, + processAnnotated(AnnotationLookup.PRE_DESTROY, object, object.getClass(), objectSupplier, null, new ArrayList<>(5)); - ArrayList> requestors = new ArrayList<>(); + List> requestors = new ArrayList<>(); processClassHierarchy(object, objectSupplier, null, true /* track */, false /* inverse order */, requestors); for (Requestor requestor : requestors) { @@ -353,8 +349,7 @@ private Object internalMake(Class clazz, PrimaryObjectSupplier objectSupplier if (shouldDebug) classesBeingCreated.add(clazz); - boolean isSingleton = isAnyAnnotationPresent(clazz, - List.of(javax.inject.Singleton.class, jakarta.inject.Singleton.class)); + boolean isSingleton = isAnnotationPresent(clazz, AnnotationLookup.SINGLETON); if (isSingleton) { synchronized (singletonCache) { if (singletonCache.containsKey(clazz)) @@ -375,11 +370,10 @@ private Object internalMake(Class clazz, PrimaryObjectSupplier objectSupplier continue; // unless this is the default constructor, it has to be tagged - if (!isAnyAnnotationPresent(constructor, - List.of(javax.inject.Inject.class, jakarta.inject.Inject.class)) - && constructor.getParameterTypes().length != 0) + if (!isAnnotationPresent(constructor, AnnotationLookup.INJECT) + && constructor.getParameterTypes().length != 0) { continue; - + } ConstructorRequestor requestor = new ConstructorRequestor(constructor, this, objectSupplier, tempSupplier); Object[] actualArgs = resolveArgs(requestor, objectSupplier, tempSupplier, false, true, false); if (unresolved(actualArgs) != -1) @@ -437,9 +431,7 @@ public void disposed(PrimaryObjectSupplier objectSupplier) { Object object = objects[i]; if (!forgetInjectedObject(object, objectSupplier)) continue; // not injected at this time - processAnnotated(javax.annotation.PreDestroy.class, object, object.getClass(), objectSupplier, null, - new ArrayList<>(5)); - processAnnotated(jakarta.annotation.PreDestroy.class, object, object.getClass(), objectSupplier, null, + processAnnotated(AnnotationLookup.PRE_DESTROY, object, object.getClass(), objectSupplier, null, new ArrayList<>(5)); } forgetSupplier(objectSupplier); @@ -494,9 +486,10 @@ private Object[] resolveArgs(Requestor requestor, PrimaryObjectSupplier objec // 1) check if we have a Provider for (int i = 0; i < actualArgs.length; i++) { Class providerClass = getProviderType(descriptors[i].getDesiredType()); - if (providerClass == null) + if (providerClass == null) { continue; - actualArgs[i] = new ProviderImpl>(descriptors[i], this, objectSupplier); + } + actualArgs[i] = AnnotationLookup.getProvider(descriptors[i], this, objectSupplier); } // 2) try extended suppliers @@ -689,7 +682,7 @@ private boolean processFields(Object userObject, PrimaryObjectSupplier objectSup continue; injectedStatic = true; } - if (!isAnyAnnotationPresent(field, List.of(javax.inject.Inject.class, jakarta.inject.Inject.class))) { + if (!isAnnotationPresent(field, AnnotationLookup.INJECT)) { continue; } requestors.add(new FieldRequestor(field, this, objectSupplier, tempSupplier, userObject, track)); @@ -708,9 +701,8 @@ private boolean processMethods(final Object userObject, PrimaryObjectSupplier ob for (Method method : methods) { Boolean isOverridden = null; - Map methodMap = null; Class originalClass = userObject.getClass(); - methodMap = isOverriddenCache.get(originalClass); + Map methodMap = isOverriddenCache.get(originalClass); if (methodMap != null) { isOverridden = methodMap.get(method); } @@ -732,7 +724,7 @@ private boolean processMethods(final Object userObject, PrimaryObjectSupplier ob } injectedStatic = true; } - if (!isAnyAnnotationPresent(method, List.of(javax.inject.Inject.class, jakarta.inject.Inject.class))) { + if (!isAnnotationPresent(method, AnnotationLookup.INJECT)) { continue; } requestors.add(new MethodRequestor(method, this, objectSupplier, tempSupplier, userObject, track)); @@ -743,7 +735,7 @@ private boolean processMethods(final Object userObject, PrimaryObjectSupplier ob /** * Checks if a given method is overridden with an injectable method. */ - private boolean isOverridden(Method method, ArrayList> classHierarchy) { + private boolean isOverridden(Method method, List> classHierarchy) { int modifiers = method.getModifiers(); if (Modifier.isPrivate(modifiers)) return false; @@ -848,8 +840,9 @@ private Class getProviderType(Type type) { if (!(type instanceof ParameterizedType)) return null; Type rawType = ((ParameterizedType) type).getRawType(); - if (!javax.inject.Provider.class.equals(rawType) && !jakarta.inject.Provider.class.equals(rawType)) + if (!AnnotationLookup.isProvider(rawType)) { return null; + } Type[] actualTypes = ((ParameterizedType) type).getActualTypeArguments(); if (actualTypes.length != 1) return null; @@ -893,17 +886,12 @@ private Binding findBinding(IObjectDescriptor descriptor) { if (desiredClass == null) desiredClass = getDesiredClass(descriptor.getDesiredType()); synchronized (bindings) { - if (!bindings.containsKey(desiredClass)) - return null; Set collection = bindings.get(desiredClass); - String desiredQualifierName = null; - if (descriptor.hasQualifier(jakarta.inject.Named.class)) { - jakarta.inject.Named namedAnnotation = descriptor.getQualifier(jakarta.inject.Named.class); - desiredQualifierName = namedAnnotation.value(); - } else if (descriptor.hasQualifier(javax.inject.Named.class)) { - javax.inject.Named namedAnnotation = descriptor.getQualifier(javax.inject.Named.class); - desiredQualifierName = namedAnnotation.value(); - } else { + if (collection == null) { + return null; + } + String desiredQualifierName = AnnotationLookup.getQualifierValue(descriptor); + if (desiredQualifierName == null) { Annotation[] annotations = descriptor.getQualifiers(); if (annotations != null) { for (Annotation annotation : annotations) { @@ -941,7 +929,9 @@ private boolean eq(String str1, String str2) { return str1.equals(str2); } - private void processAnnotated(Class annotation, Object userObject, Class objectClass, PrimaryObjectSupplier objectSupplier, PrimaryObjectSupplier tempSupplier, ArrayList> classHierarchy) { + private void processAnnotated(AnnotationProxy annotation, Object userObject, Class objectClass, + PrimaryObjectSupplier objectSupplier, PrimaryObjectSupplier tempSupplier, + List> classHierarchy) { Class superClass = objectClass.getSuperclass(); if (superClass != null && !superClass.getName().equals(JAVA_OBJECT)) { classHierarchy.add(objectClass); @@ -953,14 +943,16 @@ private void processAnnotated(Class annotation, Object use if (!isAnnotationPresent(method, annotation)) { if (shouldDebug) { for (Annotation a : method.getAnnotations()) { - if (annotation.getName().equals(a.annotationType().getName())) { + 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(describeClass(annotation)); + 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); } @@ -975,8 +967,9 @@ private void processAnnotated(Class annotation, Object use Object[] actualArgs = resolveArgs(requestor, objectSupplier, tempSupplier, false, false, false); int unresolved = unresolved(actualArgs); if (unresolved != -1) { - if (isAnnotationPresent(method, Optional.class)) + if (isAnnotationPresent(method, AnnotationLookup.OPTIONAL)) { continue; + } reportUnresolvedArgument(requestor, unresolved); } requestor.setResolvedArgs(actualArgs); @@ -985,7 +978,7 @@ private void processAnnotated(Class annotation, Object use } /** Provide a human-meaningful description of the provided class */ - private String describeClass(Class cl) { + 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$ @@ -1005,32 +998,10 @@ public void setDefaultSupplier(PrimaryObjectSupplier objectSupplier) { defaultSupplier = objectSupplier; } - private boolean isAnyAnnotationPresent(AnnotatedElement annotatedElement, - Collection> annotation) { - for (Class a : annotation) { - if (isAnnotationPresent(annotatedElement, a)) { - return true; - } - } - return false; - } - - private boolean isAnnotationPresent(AnnotatedElement annotatedElement, - Class annotation) { - Map cache = annotationsPresent.get(annotation); - if (cache == null) { - cache = Collections.synchronizedMap(new WeakHashMap<>()); - annotationsPresent.put(annotation, cache); - } - - Boolean present = cache.get(annotatedElement); - if (present != null) { - return present; - } - - boolean isPresent = annotatedElement.isAnnotationPresent(annotation); - cache.put(annotatedElement, isPresent); - return isPresent; + private boolean isAnnotationPresent(AnnotatedElement annotatedElement, AnnotationProxy lookUp) { + Map cache = annotationsPresent.computeIfAbsent(lookUp, + a -> Collections.synchronizedMap(new WeakHashMap<>())); + return cache.computeIfAbsent(annotatedElement, lookUp::isPresent); } /** diff --git a/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/ObjectDescriptor.java b/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/ObjectDescriptor.java index e03d80932a3..1994eb27041 100644 --- a/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/ObjectDescriptor.java +++ b/runtime/bundles/org.eclipse.e4.core.di/src/org/eclipse/e4/core/internal/di/ObjectDescriptor.java @@ -96,9 +96,9 @@ private Annotation[] qualifiers(Annotation[] allAnnotations) { Annotation[] result; List qualifiers = new ArrayList<>(); for (Annotation annotation : allAnnotations) { - if (annotation.annotationType().isAnnotationPresent(javax.inject.Qualifier.class) - || annotation.annotationType().isAnnotationPresent(jakarta.inject.Qualifier.class)) + if (AnnotationLookup.QUALIFIER.isPresent(annotation.annotationType())) { qualifiers.add(annotation); + } } if (qualifiers.isEmpty()) return null; 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 index cf0347c34ed..d137cec1c7e 100644 --- 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 @@ -13,11 +13,12 @@ *******************************************************************************/ 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 javax.inject.Provider, jakarta.inject.Provider { +public class ProviderImpl implements Provider { final private PrimaryObjectSupplier objectProvider; final private IObjectDescriptor objectDescriptor; diff --git a/runtime/bundles/org.eclipse.e4.core.services/META-INF/MANIFEST.MF b/runtime/bundles/org.eclipse.e4.core.services/META-INF/MANIFEST.MF index 7b70cf56aba..534b78bd7d9 100644 --- a/runtime/bundles/org.eclipse.e4.core.services/META-INF/MANIFEST.MF +++ b/runtime/bundles/org.eclipse.e4.core.services/META-INF/MANIFEST.MF @@ -9,8 +9,7 @@ Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-17 Import-Package: jakarta.annotation;version="[2.0.0,3.0.0)", jakarta.inject;version="[2.0.0,3.0.0)", - javax.annotation;version="[1.3.0,2.0.0)", - javax.inject;version="[1.0.0,2.0.0)", + javax.inject;version="[1.0.0,2.0.0)";resolution:=optional, org.eclipse.osgi.service.debug;version="1.1.0", org.eclipse.osgi.service.localization;version="1.1.0", org.eclipse.osgi.util;version="[1.1.0,2.0.0)", diff --git a/runtime/bundles/org.eclipse.e4.core.services/src/org/eclipse/e4/core/internal/services/MessageFactoryServiceImpl.java b/runtime/bundles/org.eclipse.e4.core.services/src/org/eclipse/e4/core/internal/services/MessageFactoryServiceImpl.java index 703c2f24db8..4405769d301 100644 --- a/runtime/bundles/org.eclipse.e4.core.services/src/org/eclipse/e4/core/internal/services/MessageFactoryServiceImpl.java +++ b/runtime/bundles/org.eclipse.e4.core.services/src/org/eclipse/e4/core/internal/services/MessageFactoryServiceImpl.java @@ -250,12 +250,12 @@ private M createInstance(Locale locale, Class messages, Message annotatio * @param messageClass * The type of the message class whose instance is requested. */ + @SuppressWarnings("restriction") private void processPostConstruct(Object messageObject, Class messageClass) { if (messageObject != null) { Method[] methods = messageClass.getDeclaredMethods(); for (Method method : methods) { - if (!method.isAnnotationPresent(javax.annotation.PostConstruct.class) - && !method.isAnnotationPresent(jakarta.annotation.PostConstruct.class)) { + if (!org.eclipse.e4.core.internal.di.AnnotationLookup.POST_CONSTRUCT.isPresent(method)) { continue; } else { try { diff --git a/runtime/tests/org.eclipse.e4.core.tests/META-INF/MANIFEST.MF b/runtime/tests/org.eclipse.e4.core.tests/META-INF/MANIFEST.MF index 03725a4dc15..8ee486122c5 100644 --- a/runtime/tests/org.eclipse.e4.core.tests/META-INF/MANIFEST.MF +++ b/runtime/tests/org.eclipse.e4.core.tests/META-INF/MANIFEST.MF @@ -21,12 +21,14 @@ Import-Package: jakarta.annotation;version="[2.1.0,3.0.0)", org.eclipse.osgi.service.datalocation, org.eclipse.osgi.service.debug, org.junit;version="[4.13.0,5.0.0)", + org.junit.function;version="[4.13.0,5.0.0)", org.junit.runner;version="[4.13.0,5.0.0)", org.junit.runners;version="[4.13.0,5.0.0)", org.osgi.framework;version="[1.10.0,2.0.0)", org.osgi.service.component;version="1.3.0", org.osgi.service.event;version="1.3.0", org.osgi.util.tracker;version="[1.5.0,2.0.0)" +DynamicImport-Package: javax.annotation, javax.inject Export-Package: org.eclipse.e4.core.internal.tests;x-internal:=true, org.eclipse.e4.core.internal.tests.about;x-internal:=true, org.eclipse.e4.core.internal.tests.contexts;x-internal:=true, diff --git a/runtime/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/extensions/InjectionOSGiTest.java b/runtime/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/extensions/InjectionOSGiTest.java index 1731b8e8e39..822550ef5cb 100644 --- a/runtime/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/extensions/InjectionOSGiTest.java +++ b/runtime/tests/org.eclipse.e4.core.tests/src/org/eclipse/e4/core/internal/tests/di/extensions/InjectionOSGiTest.java @@ -18,6 +18,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import org.eclipse.e4.core.contexts.ContextInjectionFactory; @@ -106,6 +107,20 @@ public void setUp() throws Exception { localContext); } + @Test + public void ensureJavaxIsNotAvailable() { + // Ensure that the providing bundles of the following classes are absent of the + // test-runtime and thus the mentioned classes cannot be loaded + assertThrows(ClassNotFoundException.class, () -> Class.forName("javax.inject.Inject")); + assertThrows(ClassNotFoundException.class, () -> Class.forName("javax.inject.Singleton")); + assertThrows(ClassNotFoundException.class, () -> Class.forName("javax.inject.Qualifier")); + assertThrows(ClassNotFoundException.class, () -> Class.forName("javax.inject.Provider")); + assertThrows(ClassNotFoundException.class, () -> Class.forName("javax.inject.Named")); + + assertThrows(ClassNotFoundException.class, () -> Class.forName("javax.annotation.PreDestroy")); + assertThrows(ClassNotFoundException.class, () -> Class.forName("javax.annotation.PostConstruct")); + } + @Test public void testInject() { assertTrue(target.hasContext());