From eb0dbcfcbe9864c36214cf7ee4f142b9f980051e Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 11 Sep 2024 08:34:25 +0200 Subject: [PATCH 1/6] Code cleanup --- .../maven/di/impl/BindingInitializer.java | 2 +- .../apache/maven/di/impl/InjectorImpl.java | 9 ++--- .../apache/maven/di/impl/ReflectionUtils.java | 6 +-- .../java/org/apache/maven/di/impl/Types.java | 37 +++++++------------ 4 files changed, 21 insertions(+), 33 deletions(-) diff --git a/maven-di/src/main/java/org/apache/maven/di/impl/BindingInitializer.java b/maven-di/src/main/java/org/apache/maven/di/impl/BindingInitializer.java index a123f30cbcde..4a3eec251c7b 100644 --- a/maven-di/src/main/java/org/apache/maven/di/impl/BindingInitializer.java +++ b/maven-di/src/main/java/org/apache/maven/di/impl/BindingInitializer.java @@ -48,7 +48,7 @@ public static BindingInitializer combine(List> bind .map(BindingInitializer::getDependencies) .flatMap(Collection::stream) .collect(toSet()); - return new BindingInitializer(deps) { + return new BindingInitializer<>(deps) { @Override public Consumer compile(Function, Supplier> compiler) { return instance -> bindingInitializers.stream() diff --git a/maven-di/src/main/java/org/apache/maven/di/impl/InjectorImpl.java b/maven-di/src/main/java/org/apache/maven/di/impl/InjectorImpl.java index 143f52102e7a..a2286f9e7b59 100644 --- a/maven-di/src/main/java/org/apache/maven/di/impl/InjectorImpl.java +++ b/maven-di/src/main/java/org/apache/maven/di/impl/InjectorImpl.java @@ -88,7 +88,7 @@ public Injector discover(ClassLoader classLoader) { try (InputStream is = enumeration.nextElement().openStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(Objects.requireNonNull(is)))) { for (String line : - reader.lines().filter(l -> !l.startsWith("#")).collect(Collectors.toList())) { + reader.lines().filter(l -> !l.startsWith("#")).toList()) { Class clazz = classLoader.loadClass(line); bindImplicit(clazz); } @@ -133,7 +133,7 @@ public Injector bindImplicit(Class clazz) { return this; } - private LinkedHashSet> current = new LinkedHashSet<>(); + private final LinkedHashSet> current = new LinkedHashSet<>(); private Injector doBind(Key key, Binding binding) { if (!current.add(key)) { @@ -312,14 +312,13 @@ private static class WrappingMap extends AbstractMap { this.mapper = mapper; } - @SuppressWarnings("NullableProblems") @Override public Set> entrySet() { - return new AbstractSet>() { + return new AbstractSet<>() { @Override public Iterator> iterator() { Iterator> it = delegate.entrySet().iterator(); - return new Iterator>() { + return new Iterator<>() { @Override public boolean hasNext() { return it.hasNext(); diff --git a/maven-di/src/main/java/org/apache/maven/di/impl/ReflectionUtils.java b/maven-di/src/main/java/org/apache/maven/di/impl/ReflectionUtils.java index 4389138e398f..58198e9fd6f7 100644 --- a/maven-di/src/main/java/org/apache/maven/di/impl/ReflectionUtils.java +++ b/maven-di/src/main/java/org/apache/maven/di/impl/ReflectionUtils.java @@ -171,14 +171,14 @@ public static List getAnnotatedElements List> constructors = Arrays.asList(cls.getDeclaredConstructors()); List> injectConstructors = constructors.stream() .filter(c -> c.isAnnotationPresent(Inject.class)) - .collect(toList()); + .toList(); List factoryMethods = Arrays.stream(cls.getDeclaredMethods()) .filter(method -> method.getReturnType() == cls && Modifier.isStatic(method.getModifiers())) - .collect(toList()); + .toList(); List injectFactoryMethods = factoryMethods.stream() .filter(method -> method.isAnnotationPresent(Inject.class)) - .collect(toList()); + .toList(); if (!injectConstructors.isEmpty()) { if (injectConstructors.size() > 1) { diff --git a/maven-di/src/main/java/org/apache/maven/di/impl/Types.java b/maven-di/src/main/java/org/apache/maven/di/impl/Types.java index 735057b1ca9b..b4830bd9f8fc 100644 --- a/maven-di/src/main/java/org/apache/maven/di/impl/Types.java +++ b/maven-di/src/main/java/org/apache/maven/di/impl/Types.java @@ -186,8 +186,7 @@ public static Type bind(Type type, Function, Type> bindings) { if (type instanceof Class) { return type; } - if (type instanceof TypeVariable) { - TypeVariable typeVariable = (TypeVariable) type; + if (type instanceof TypeVariable typeVariable) { Type actualType = bindings.apply(typeVariable); if (actualType == null) { throw new TypeNotBoundException("Type variable not found: " + typeVariable + " ( " @@ -195,8 +194,7 @@ public static Type bind(Type type, Function, Type> bindings) { } return actualType; } - if (type instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) type; + if (type instanceof ParameterizedType parameterizedType) { Type[] typeArguments = parameterizedType.getActualTypeArguments(); Type[] typeArguments2 = new Type[typeArguments.length]; for (int i = 0; i < typeArguments.length; i++) { @@ -209,8 +207,7 @@ public static Type bind(Type type, Function, Type> bindings) { Type componentType = ((GenericArrayType) type).getGenericComponentType(); return new GenericArrayTypeImpl(bind(componentType, bindings)); } - if (type instanceof WildcardType) { - WildcardType wildcardType = (WildcardType) type; + if (type instanceof WildcardType wildcardType) { Type[] upperBounds = wildcardType.getUpperBounds(); Type[] upperBounds2 = new Type[upperBounds.length]; for (int i = 0; i < upperBounds.length; i++) { @@ -309,8 +306,7 @@ public static Type simplifyType(Type original) { return original; } - if (original instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) original; + if (original instanceof ParameterizedType parameterizedType) { Type[] typeArguments = parameterizedType.getActualTypeArguments(); Type[] repackedTypeArguments = simplifyTypes(typeArguments); @@ -329,8 +325,7 @@ public static Type simplifyType(Type original) { throw new IllegalArgumentException("Key should not contain a type variable: " + original); } - if (original instanceof WildcardType) { - WildcardType wildcardType = (WildcardType) original; + if (original instanceof WildcardType wildcardType) { Type[] upperBounds = wildcardType.getUpperBounds(); if (upperBounds.length == 1) { Type upperBound = upperBounds[0]; @@ -398,8 +393,7 @@ public static boolean isAssignable(Type to, Type from) { private static boolean isAssignable(Type to, Type from, boolean strict) { if (to instanceof WildcardType || from instanceof WildcardType) { Type[] toUppers, toLowers; - if (to instanceof WildcardType) { - WildcardType wildcardTo = (WildcardType) to; + if (to instanceof WildcardType wildcardTo) { toUppers = wildcardTo.getUpperBounds(); toLowers = wildcardTo.getLowerBounds(); } else { @@ -408,10 +402,9 @@ private static boolean isAssignable(Type to, Type from, boolean strict) { } Type[] fromUppers, fromLowers; - if (from instanceof WildcardType) { - WildcardType wildcardTo = (WildcardType) to; - fromUppers = wildcardTo.getUpperBounds(); - fromLowers = wildcardTo.getLowerBounds(); + if (from instanceof WildcardType wildcardFrom) { + fromUppers = wildcardFrom.getUpperBounds(); + fromLowers = wildcardFrom.getLowerBounds(); } else { fromUppers = new Type[] {from}; fromLowers = strict ? fromUppers : NO_TYPES; @@ -523,10 +516,9 @@ public int hashCode() { @Override public boolean equals(Object other) { - if (!(other instanceof ParameterizedType)) { + if (!(other instanceof ParameterizedType that)) { return false; } - ParameterizedType that = (ParameterizedType) other; return this.getRawType().equals(that.getRawType()) && Objects.equals(this.getOwnerType(), that.getOwnerType()) && Arrays.equals(this.getActualTypeArguments(), that.getActualTypeArguments()); @@ -613,10 +605,9 @@ public int hashCode() { @Override public boolean equals(Object other) { - if (!(other instanceof WildcardType)) { + if (!(other instanceof WildcardType that)) { return false; } - WildcardType that = (WildcardType) other; return Arrays.equals(this.getUpperBounds(), that.getUpperBounds()) && Arrays.equals(this.getLowerBounds(), that.getLowerBounds()); } @@ -671,10 +662,9 @@ public int hashCode() { @Override public boolean equals(Object other) { - if (!(other instanceof GenericArrayType)) { + if (!(other instanceof GenericArrayType that)) { return false; } - GenericArrayType that = (GenericArrayType) other; return this.getGenericComponentType().equals(that.getGenericComponentType()); } @@ -700,8 +690,7 @@ public static String getSimpleName(Type type) { return Arrays.stream(((ParameterizedType) type).getActualTypeArguments()) .map(Types::getSimpleName) .collect(joining(",", "<", ">")); - } else if (type instanceof WildcardType) { - WildcardType wildcardType = (WildcardType) type; + } else if (type instanceof WildcardType wildcardType) { Type[] upperBounds = wildcardType.getUpperBounds(); Type[] lowerBounds = wildcardType.getLowerBounds(); return "?" From 21d9d5801ba61e8891f7fe709a5daecdd190321d Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 11 Sep 2024 08:34:58 +0200 Subject: [PATCH 2/6] Provide build path with causes when an exception occur --- .../main/java/org/apache/maven/di/impl/Binding.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/maven-di/src/main/java/org/apache/maven/di/impl/Binding.java b/maven-di/src/main/java/org/apache/maven/di/impl/Binding.java index 97e650a4c0b9..6f2dc8092009 100644 --- a/maven-di/src/main/java/org/apache/maven/di/impl/Binding.java +++ b/maven-di/src/main/java/org/apache/maven/di/impl/Binding.java @@ -98,9 +98,13 @@ public Supplier compile(Function, Supplier> compiler) { final Supplier compiledBinding = Binding.this.compile(compiler); final Consumer consumer = bindingInitializer.compile(compiler); return () -> { - T instance = compiledBinding.get(); - consumer.accept(instance); - return instance; + try { + T instance = compiledBinding.get(); + consumer.accept(instance); + return instance; + } catch (DIException e) { + throw new DIException("Error while initializing binding " + Binding.this, e); + } }; } From 1aac4568ea0645d46149d2065379eb96048cee6d Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 11 Sep 2024 08:35:17 +0200 Subject: [PATCH 3/6] Fix toString to display meaningful info --- maven-di/src/main/java/org/apache/maven/di/impl/Binding.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maven-di/src/main/java/org/apache/maven/di/impl/Binding.java b/maven-di/src/main/java/org/apache/maven/di/impl/Binding.java index 6f2dc8092009..c34916758fd2 100644 --- a/maven-di/src/main/java/org/apache/maven/di/impl/Binding.java +++ b/maven-di/src/main/java/org/apache/maven/di/impl/Binding.java @@ -188,7 +188,7 @@ public Supplier compile(Function, Supplier> compiler) { @Override public String toString() { - return "BindingToConstructor[" + constructor + "]" + getDependencies(); + return "BindingToConstructor[" + getOriginalKey() + "]" + getDependencies(); } } } From 40b7c2143c92cdcb4db124eb6e740a10f0a8a189 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 11 Sep 2024 08:35:26 +0200 Subject: [PATCH 4/6] Javadoc --- .../java/org/apache/maven/api/di/MojoExecutionScoped.java | 7 +++++++ .../main/java/org/apache/maven/api/di/SessionScoped.java | 2 ++ 2 files changed, 9 insertions(+) diff --git a/api/maven-api-di/src/main/java/org/apache/maven/api/di/MojoExecutionScoped.java b/api/maven-api-di/src/main/java/org/apache/maven/api/di/MojoExecutionScoped.java index a3fec5ca81ce..729ec5e10780 100644 --- a/api/maven-api-di/src/main/java/org/apache/maven/api/di/MojoExecutionScoped.java +++ b/api/maven-api-di/src/main/java/org/apache/maven/api/di/MojoExecutionScoped.java @@ -29,6 +29,13 @@ /** * Indicates that the annotated bean has a lifespan limited to a given mojo execution, * which means each mojo execution will result in a different instance being injected. + *

+ * The following objects will be bound to the mojo execution scope: + *

    + *
  • {@code org.apache.maven.api.MojoExecution}
  • + *
  • {@code org.apache.maven.api.Project}
  • + *
  • {@code org.apache.maven.api.plugin.Log}
  • + *
* * @since 4.0.0 */ diff --git a/api/maven-api-di/src/main/java/org/apache/maven/api/di/SessionScoped.java b/api/maven-api-di/src/main/java/org/apache/maven/api/di/SessionScoped.java index eae72f8e2b64..06c1049683ed 100644 --- a/api/maven-api-di/src/main/java/org/apache/maven/api/di/SessionScoped.java +++ b/api/maven-api-di/src/main/java/org/apache/maven/api/di/SessionScoped.java @@ -29,6 +29,8 @@ /** * Indicates that annotated component should be instantiated before session execution starts * and discarded after session execution completes. + *

+ * A {@code org.apache.maven.api.Session} object is available in the scope of this annotation. * * @since 4.0.0 */ From a12e6da3923d312b610f30c54df1a7021a49b922 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 11 Sep 2024 08:58:58 +0200 Subject: [PATCH 5/6] Add @NonNull and @Overrides annotations --- maven-di/src/main/java/org/apache/maven/di/Injector.java | 7 +++++++ .../main/java/org/apache/maven/di/impl/InjectorImpl.java | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/maven-di/src/main/java/org/apache/maven/di/Injector.java b/maven-di/src/main/java/org/apache/maven/di/Injector.java index 9ccc1b698cc3..d4eea9862526 100644 --- a/maven-di/src/main/java/org/apache/maven/di/Injector.java +++ b/maven-di/src/main/java/org/apache/maven/di/Injector.java @@ -21,6 +21,7 @@ import java.lang.annotation.Annotation; import java.util.function.Supplier; +import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.di.impl.InjectorImpl; public interface Injector { @@ -29,18 +30,24 @@ public interface Injector { // Builder API // + @Nonnull static Injector create() { return new InjectorImpl(); } + @Nonnull Injector discover(ClassLoader classLoader); + @Nonnull Injector bindScope(Class scopeAnnotation, Scope scope); + @Nonnull Injector bindScope(Class scopeAnnotation, Supplier scope); + @Nonnull Injector bindImplicit(Class cls); + @Nonnull Injector bindInstance(Class cls, T instance); // diff --git a/maven-di/src/main/java/org/apache/maven/di/impl/InjectorImpl.java b/maven-di/src/main/java/org/apache/maven/di/impl/InjectorImpl.java index a2286f9e7b59..9cbf35706294 100644 --- a/maven-di/src/main/java/org/apache/maven/di/impl/InjectorImpl.java +++ b/maven-di/src/main/java/org/apache/maven/di/impl/InjectorImpl.java @@ -64,10 +64,12 @@ public InjectorImpl() { bindScope(Singleton.class, new SingletonScope()); } + @Override public T getInstance(Class key) { return getInstance(Key.of(key)); } + @Override public T getInstance(Key key) { return getCompiledBinding(key).get(); } @@ -100,10 +102,12 @@ public Injector discover(ClassLoader classLoader) { return this; } + @Override public Injector bindScope(Class scopeAnnotation, Scope scope) { return bindScope(scopeAnnotation, () -> scope); } + @Override public Injector bindScope(Class scopeAnnotation, Supplier scope) { if (scopes.put(scopeAnnotation, scope) != null) { throw new DIException( @@ -112,6 +116,7 @@ public Injector bindScope(Class scopeAnnotation, Supplier< return this; } + @Override public Injector bindInstance(Class clazz, U instance) { Key key = Key.of(clazz, ReflectionUtils.qualifierOf(clazz)); Binding binding = Binding.toInstance(instance); From a646bdd01c40d01f87bed7a23bf750aea4984adb Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 11 Sep 2024 09:53:04 +0200 Subject: [PATCH 6/6] Support for @Nullable on fields and parameters --- .../maven/api/annotations/Nullable.java | 2 +- .../internal/impl/SisuDiBridgeModule.java | 7 ++- .../org/apache/maven/di/impl/Binding.java | 31 ++++++------ .../maven/di/impl/BindingInitializer.java | 14 +++--- .../org/apache/maven/di/impl/Dependency.java | 31 ++++++++++++ .../apache/maven/di/impl/InjectorImpl.java | 8 +++- .../apache/maven/di/impl/ReflectionUtils.java | 40 ++++++++-------- .../maven/di/impl/InjectorImplTest.java | 47 +++++++++++++++++++ 8 files changed, 133 insertions(+), 47 deletions(-) create mode 100644 maven-di/src/main/java/org/apache/maven/di/impl/Dependency.java diff --git a/api/maven-api-meta/src/main/java/org/apache/maven/api/annotations/Nullable.java b/api/maven-api-meta/src/main/java/org/apache/maven/api/annotations/Nullable.java index 32e4bd75e728..df2517cad9aa 100644 --- a/api/maven-api-meta/src/main/java/org/apache/maven/api/annotations/Nullable.java +++ b/api/maven-api-meta/src/main/java/org/apache/maven/api/annotations/Nullable.java @@ -30,5 +30,5 @@ */ @Experimental @Documented -@Retention(RetentionPolicy.CLASS) +@Retention(RetentionPolicy.RUNTIME) public @interface Nullable {} diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/SisuDiBridgeModule.java b/maven-core/src/main/java/org/apache/maven/internal/impl/SisuDiBridgeModule.java index ab12ee5b62d8..eb6789c12bff 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/SisuDiBridgeModule.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/SisuDiBridgeModule.java @@ -48,6 +48,7 @@ import org.apache.maven.di.Key; import org.apache.maven.di.impl.Binding; import org.apache.maven.di.impl.DIException; +import org.apache.maven.di.impl.Dependency; import org.apache.maven.di.impl.InjectorImpl; import org.apache.maven.execution.scope.internal.MojoExecutionScope; import org.apache.maven.session.scope.internal.SessionScope; @@ -66,7 +67,8 @@ protected void configure() { injector = new InjectorImpl() { @Override - public Supplier getCompiledBinding(Key key) { + public Supplier getCompiledBinding(Dependency dep) { + Key key = dep.key(); Set> res = getBindings(key); if (res != null && !res.isEmpty()) { List> bindingList = new ArrayList<>(res); @@ -119,6 +121,9 @@ public Supplier getCompiledBinding(Key key) { // ignore e.printStackTrace(); } + if (dep.optional()) { + return () -> null; + } throw new DIException("No binding to construct an instance for key " + key.getDisplayString() + ". Existing bindings:\n" + getBoundKeys().stream() diff --git a/maven-di/src/main/java/org/apache/maven/di/impl/Binding.java b/maven-di/src/main/java/org/apache/maven/di/impl/Binding.java index c34916758fd2..204c07af6741 100644 --- a/maven-di/src/main/java/org/apache/maven/di/impl/Binding.java +++ b/maven-di/src/main/java/org/apache/maven/di/impl/Binding.java @@ -34,16 +34,16 @@ import static java.util.stream.Collectors.joining; public abstract class Binding { - private final Set> dependencies; + private final Set> dependencies; private Annotation scope; private int priority; private Key originalKey; - protected Binding(Key originalKey, Set> dependencies) { + protected Binding(Key originalKey, Set> dependencies) { this(originalKey, dependencies, null, 0); } - protected Binding(Key originalKey, Set> dependencies, Annotation scope, int priority) { + protected Binding(Key originalKey, Set> dependencies, Annotation scope, int priority) { this.originalKey = originalKey; this.dependencies = dependencies; this.scope = scope; @@ -56,15 +56,18 @@ public static Binding toInstance(T instance) { public static Binding to(Key originalKey, TupleConstructorN constructor, Class[] types) { return Binding.to( - originalKey, constructor, Stream.of(types).map(Key::of).toArray(Key[]::new)); + originalKey, + constructor, + Stream.of(types).map(c -> new Dependency<>(Key.of(c), false)).toArray(Dependency[]::new)); } - public static Binding to(Key originalKey, TupleConstructorN constructor, Key[] dependencies) { + public static Binding to( + Key originalKey, TupleConstructorN constructor, Dependency[] dependencies) { return to(originalKey, constructor, dependencies, 0); } public static Binding to( - Key originalKey, TupleConstructorN constructor, Key[] dependencies, int priority) { + Key originalKey, TupleConstructorN constructor, Dependency[] dependencies, int priority) { return new BindingToConstructor<>(originalKey, constructor, dependencies, priority); } @@ -94,7 +97,7 @@ public Binding initializeWith(BindingInitializer bindingInitializer) { this.scope, this.priority) { @Override - public Supplier compile(Function, Supplier> compiler) { + public Supplier compile(Function, Supplier> compiler) { final Supplier compiledBinding = Binding.this.compile(compiler); final Consumer consumer = bindingInitializer.compile(compiler); return () -> { @@ -115,9 +118,9 @@ public String toString() { }; } - public abstract Supplier compile(Function, Supplier> compiler); + public abstract Supplier compile(Function, Supplier> compiler); - public Set> getDependencies() { + public Set> getDependencies() { return dependencies; } @@ -126,7 +129,7 @@ public Annotation getScope() { } public String getDisplayString() { - return dependencies.stream().map(Key::getDisplayString).collect(joining(", ", "[", "]")); + return dependencies.stream().map(Dependency::getDisplayString).collect(joining(", ", "[", "]")); } public Key getOriginalKey() { @@ -156,7 +159,7 @@ public BindingToInstance(T instance) { } @Override - public Supplier compile(Function, Supplier> compiler) { + public Supplier compile(Function, Supplier> compiler) { return () -> instance; } @@ -168,17 +171,17 @@ public String toString() { public static class BindingToConstructor extends Binding { final TupleConstructorN constructor; - final Key[] args; + final Dependency[] args; BindingToConstructor( - Key key, TupleConstructorN constructor, Key[] dependencies, int priority) { + Key key, TupleConstructorN constructor, Dependency[] dependencies, int priority) { super(key, new HashSet<>(Arrays.asList(dependencies)), null, priority); this.constructor = constructor; this.args = dependencies; } @Override - public Supplier compile(Function, Supplier> compiler) { + public Supplier compile(Function, Supplier> compiler) { return () -> { Object[] args = Stream.of(this.args).map(compiler).map(Supplier::get).toArray(); diff --git a/maven-di/src/main/java/org/apache/maven/di/impl/BindingInitializer.java b/maven-di/src/main/java/org/apache/maven/di/impl/BindingInitializer.java index 4a3eec251c7b..ccf5530ab4ce 100644 --- a/maven-di/src/main/java/org/apache/maven/di/impl/BindingInitializer.java +++ b/maven-di/src/main/java/org/apache/maven/di/impl/BindingInitializer.java @@ -25,32 +25,30 @@ import java.util.function.Function; import java.util.function.Supplier; -import org.apache.maven.di.Key; - import static java.util.stream.Collectors.toSet; public abstract class BindingInitializer { - private final Set> dependencies; + private final Set> dependencies; - protected BindingInitializer(Set> dependencies) { + protected BindingInitializer(Set> dependencies) { this.dependencies = dependencies; } - public Set> getDependencies() { + public Set> getDependencies() { return dependencies; } - public abstract Consumer compile(Function, Supplier> compiler); + public abstract Consumer compile(Function, Supplier> compiler); public static BindingInitializer combine(List> bindingInitializers) { - Set> deps = bindingInitializers.stream() + Set> deps = bindingInitializers.stream() .map(BindingInitializer::getDependencies) .flatMap(Collection::stream) .collect(toSet()); return new BindingInitializer<>(deps) { @Override - public Consumer compile(Function, Supplier> compiler) { + public Consumer compile(Function, Supplier> compiler) { return instance -> bindingInitializers.stream() .map(bindingInitializer -> bindingInitializer.compile(compiler)) .forEach(i -> i.accept(instance)); diff --git a/maven-di/src/main/java/org/apache/maven/di/impl/Dependency.java b/maven-di/src/main/java/org/apache/maven/di/impl/Dependency.java new file mode 100644 index 000000000000..4dc9052fa5ca --- /dev/null +++ b/maven-di/src/main/java/org/apache/maven/di/impl/Dependency.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.maven.di.impl; + +import org.apache.maven.di.Key; + +public record Dependency(Key key, boolean optional) { + public String getDisplayString() { + String s = key.getDisplayString(); + if (optional) { + s = "?" + s; + } + return s; + } +} diff --git a/maven-di/src/main/java/org/apache/maven/di/impl/InjectorImpl.java b/maven-di/src/main/java/org/apache/maven/di/impl/InjectorImpl.java index 9cbf35706294..1e047f55c211 100644 --- a/maven-di/src/main/java/org/apache/maven/di/impl/InjectorImpl.java +++ b/maven-di/src/main/java/org/apache/maven/di/impl/InjectorImpl.java @@ -71,7 +71,7 @@ public T getInstance(Class key) { @Override public T getInstance(Key key) { - return getCompiledBinding(key).get(); + return getCompiledBinding(new Dependency<>(key, false)).get(); } @SuppressWarnings("unchecked") @@ -180,7 +180,8 @@ public Map, Set>> getBindings() { return bindings; } - public Supplier getCompiledBinding(Key key) { + public Supplier getCompiledBinding(Dependency dep) { + Key key = dep.key(); Set> res = getBindings(key); if (res != null && !res.isEmpty()) { List> bindingList = new ArrayList<>(res); @@ -216,6 +217,9 @@ public Supplier getCompiledBinding(Key key) { return () -> (Q) map(map); } } + if (dep.optional()) { + return () -> null; + } throw new DIException("No binding to construct an instance for key " + key.getDisplayString() + ". Existing bindings:\n" + getBoundKeys().stream() diff --git a/maven-di/src/main/java/org/apache/maven/di/impl/ReflectionUtils.java b/maven-di/src/main/java/org/apache/maven/di/impl/ReflectionUtils.java index 58198e9fd6f7..3d49a28ed625 100644 --- a/maven-di/src/main/java/org/apache/maven/di/impl/ReflectionUtils.java +++ b/maven-di/src/main/java/org/apache/maven/di/impl/ReflectionUtils.java @@ -240,10 +240,12 @@ public static BindingInitializer generateInjectingInitializer(Key cont public static BindingInitializer fieldInjector(Key container, Field field) { field.setAccessible(true); Key key = keyOf(container.getType(), field.getGenericType(), field); - return new BindingInitializer(Collections.singleton(key)) { + boolean optional = field.isAnnotationPresent(Nullable.class); + Dependency dep = new Dependency<>(key, optional); + return new BindingInitializer(Collections.singleton(dep)) { @Override - public Consumer compile(Function, Supplier> compiler) { - Supplier binding = compiler.apply(key); + public Consumer compile(Function, Supplier> compiler) { + Supplier binding = compiler.apply(dep); return (T instance) -> { Object arg = binding.get(); try { @@ -258,10 +260,10 @@ public Consumer compile(Function, Supplier> compiler) { public static BindingInitializer methodInjector(Key container, Method method) { method.setAccessible(true); - Key[] dependencies = toDependencies(container.getType(), method); + Dependency[] dependencies = toDependencies(container.getType(), method); return new BindingInitializer(new HashSet<>(Arrays.asList(dependencies))) { @Override - public Consumer compile(Function, Supplier> compiler) { + public Consumer compile(Function, Supplier> compiler) { return instance -> { Object[] args = getDependencies().stream() .map(compiler) @@ -279,35 +281,31 @@ public Consumer compile(Function, Supplier> compiler) { }; } - public static Key[] toDependencies(@Nullable Type container, Executable executable) { - Key[] keys = toArgDependencies(container, executable); + public static Dependency[] toDependencies(@Nullable Type container, Executable executable) { + Dependency[] keys = toArgDependencies(container, executable); if (executable instanceof Constructor || Modifier.isStatic(executable.getModifiers())) { return keys; } else { - Key[] nkeys = new Key[keys.length + 1]; - nkeys[0] = Key.ofType(container); + Dependency[] nkeys = new Dependency[keys.length + 1]; + nkeys[0] = new Dependency<>(Key.ofType(container), false); System.arraycopy(keys, 0, nkeys, 1, keys.length); return nkeys; } } - private static Key[] toArgDependencies(@Nullable Type container, Executable executable) { + private static Dependency[] toArgDependencies(@Nullable Type container, Executable executable) { Parameter[] parameters = executable.getParameters(); - Key[] dependencies = new Key[parameters.length]; + Dependency[] dependencies = new Dependency[parameters.length]; if (parameters.length == 0) { return dependencies; } - Type type = parameters[0].getParameterizedType(); - Parameter parameter = parameters[0]; - dependencies[0] = keyOf(container, type, parameter); - Type[] genericParameterTypes = executable.getGenericParameterTypes(); - boolean hasImplicitDependency = genericParameterTypes.length != parameters.length; - for (int i = 1; i < dependencies.length; i++) { - type = genericParameterTypes[hasImplicitDependency ? i - 1 : i]; - parameter = parameters[i]; - dependencies[i] = keyOf(container, type, parameter); + for (int i = 0; i < dependencies.length; i++) { + Type type = genericParameterTypes[i]; + Parameter parameter = parameters[i]; + boolean optional = parameter.isAnnotationPresent(Nullable.class); + dependencies[i] = new Dependency<>(keyOf(container, type, parameter), optional); } return dependencies; } @@ -353,7 +351,7 @@ public static Binding bindingFromMethod(Method method) { public static Binding bindingFromConstructor(Key key, Constructor constructor) { constructor.setAccessible(true); - Key[] dependencies = toDependencies(key.getType(), constructor); + Dependency[] dependencies = toDependencies(key.getType(), constructor); Binding binding = Binding.to( key, diff --git a/maven-di/src/test/java/org/apache/maven/di/impl/InjectorImplTest.java b/maven-di/src/test/java/org/apache/maven/di/impl/InjectorImplTest.java index b732fe8f5de8..0d22acfb9213 100644 --- a/maven-di/src/test/java/org/apache/maven/di/impl/InjectorImplTest.java +++ b/maven-di/src/test/java/org/apache/maven/di/impl/InjectorImplTest.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import org.apache.maven.api.annotations.Nullable; import org.apache.maven.api.di.Inject; import org.apache.maven.api.di.Named; import org.apache.maven.api.di.Priority; @@ -42,6 +43,7 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertNull; @SuppressWarnings("unused") public class InjectorImplTest { @@ -328,4 +330,49 @@ static class Another {} @Named static class Third {} } + + @Test + void testNullableOnField() { + Injector injector = Injector.create().bindImplicit(NullableOnField.class); + NullableOnField.MyMojo mojo = injector.getInstance(NullableOnField.MyMojo.class); + assertNotNull(mojo); + assertNull(mojo.service); + } + + static class NullableOnField { + + @Named + interface MyService {} + + @Named + static class MyMojo { + @Inject + @Nullable + MyService service; + } + } + + @Test + void testNullableOnConstructor() { + Injector injector = Injector.create().bindImplicit(NullableOnConstructor.class); + NullableOnConstructor.MyMojo mojo = injector.getInstance(NullableOnConstructor.MyMojo.class); + assertNotNull(mojo); + assertNull(mojo.service); + } + + static class NullableOnConstructor { + + @Named + interface MyService {} + + @Named + static class MyMojo { + private final MyService service; + + @Inject + public MyMojo(@Nullable MyService service) { + this.service = service; + } + } + } }