diff --git a/maven-api-impl/pom.xml b/maven-api-impl/pom.xml index 8cc4830f86fa..69b7355e57e2 100644 --- a/maven-api-impl/pom.xml +++ b/maven-api-impl/pom.xml @@ -71,6 +71,10 @@ under the License. org.apache.maven maven-api-settings + + org.apache.maven + maven-di + org.apache.maven.resolver maven-resolver-api @@ -132,11 +136,6 @@ under the License. org.assertj assertj-core - - org.apache.maven - maven-di - test - org.apache.maven.resolver maven-resolver-named-locks diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/di/MojoExecutionScope.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/di/MojoExecutionScope.java new file mode 100644 index 000000000000..2128db3ac0ce --- /dev/null +++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/di/MojoExecutionScope.java @@ -0,0 +1,124 @@ +/* + * 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.internal.impl.di; + +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; +import java.util.function.Supplier; + +import org.apache.maven.api.annotations.Nonnull; +import org.apache.maven.di.Key; +import org.apache.maven.di.Scope; +import org.apache.maven.di.impl.DIException; + +/** + * MojoExecutionScope + */ +public class MojoExecutionScope implements Scope { + + protected static final class ScopeState { + private final Map, Supplier> seeded = new HashMap<>(); + + private final Map, Object> provided = new HashMap<>(); + + public void seed(Class clazz, Supplier value) { + seeded.put(Key.of(clazz), value); + } + + public Collection provided() { + return provided.values(); + } + } + + private final ThreadLocal> values = new ThreadLocal<>(); + + public MojoExecutionScope() {} + + public static Supplier seededKeySupplier(Class clazz) { + return () -> { + throw new IllegalStateException( + "No instance of " + clazz.getName() + " is bound to the mojo execution scope."); + }; + } + + public void enter() { + LinkedList stack = values.get(); + if (stack == null) { + stack = new LinkedList<>(); + values.set(stack); + } + stack.addFirst(new ScopeState()); + } + + protected ScopeState getScopeState() { + LinkedList stack = values.get(); + if (stack == null || stack.isEmpty()) { + throw new IllegalStateException(); + } + return stack.getFirst(); + } + + public void exit() { + final LinkedList stack = values.get(); + if (stack == null || stack.isEmpty()) { + throw new IllegalStateException(); + } + stack.removeFirst(); + if (stack.isEmpty()) { + values.remove(); + } + } + + public void seed(Class clazz, Supplier value) { + getScopeState().seed(clazz, value); + } + + public void seed(Class clazz, final T value) { + seed(clazz, (Supplier) () -> value); + } + + @SuppressWarnings("unchecked") + @Nonnull + public Supplier scope(@Nonnull Key key, @Nonnull Supplier unscoped) { + return () -> { + LinkedList stack = values.get(); + if (stack == null || stack.isEmpty()) { + throw new DIException("Cannot access " + key + " outside of a scoping block"); + } + + ScopeState state = stack.getFirst(); + + Supplier seeded = state.seeded.get(key); + + if (seeded != null) { + return (T) seeded.get(); + } + + T provided = (T) state.provided.get(key); + if (provided == null && unscoped != null) { + provided = unscoped.get(); + state.provided.put(key, provided); + } + + return provided; + }; + } +} diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/di/OutOfScopeException.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/di/OutOfScopeException.java new file mode 100644 index 000000000000..c437425415d2 --- /dev/null +++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/di/OutOfScopeException.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.internal.impl.di; + +import org.apache.maven.di.impl.DIException; + +public class OutOfScopeException extends DIException { + public OutOfScopeException(String message) { + super(message); + } + + public OutOfScopeException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/maven-api-impl/src/main/java/org/apache/maven/internal/impl/di/SessionScope.java b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/di/SessionScope.java new file mode 100644 index 000000000000..b5378f824ad4 --- /dev/null +++ b/maven-api-impl/src/main/java/org/apache/maven/internal/impl/di/SessionScope.java @@ -0,0 +1,183 @@ +/* + * 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.internal.impl.di; + +import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import org.apache.maven.di.Key; +import org.apache.maven.di.Scope; +import org.apache.maven.di.impl.Types; + +public class SessionScope implements Scope { + + /** + * ScopeState + */ + protected static final class ScopeState { + private final Map, CachingProvider> provided = new ConcurrentHashMap<>(); + + public void seed(Class clazz, Supplier value) { + provided.put(Key.of(clazz), new CachingProvider<>(value)); + } + + @SuppressWarnings("unchecked") + public Supplier scope(Key key, Supplier unscoped) { + Supplier provider = provided.computeIfAbsent(key, k -> new CachingProvider<>(unscoped)); + return (Supplier) provider; + } + + public Collection> providers() { + return provided.values(); + } + } + + protected final List values = new CopyOnWriteArrayList<>(); + + public void enter() { + values.add(0, new ScopeState()); + } + + protected ScopeState getScopeState() { + if (values.isEmpty()) { + throw new OutOfScopeException("Cannot access session scope outside of a scoping block"); + } + return values.get(0); + } + + public void exit() { + if (values.isEmpty()) { + throw new IllegalStateException(); + } + values.remove(0); + } + + public void seed(Class clazz, Supplier value) { + getScopeState().seed(clazz, value); + } + + public void seed(Class clazz, T value) { + seed(clazz, (Supplier) () -> value); + } + + @Override + public Supplier scope(Key key, Supplier unscoped) { + // Lazy evaluating provider + return () -> { + if (values.isEmpty()) { + return createProxy(key, unscoped); + } else { + return getScopeState().scope(key, unscoped).get(); + } + }; + } + + @SuppressWarnings("unchecked") + protected T createProxy(Key key, Supplier unscoped) { + InvocationHandler dispatcher = (proxy, method, args) -> { + method.setAccessible(true); + try { + return method.invoke(getScopeState().scope(key, unscoped).get(), args); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + }; + Class superType = (Class) Types.getRawType(key.getType()); + Class[] interfaces = getInterfaces(superType); + return (T) java.lang.reflect.Proxy.newProxyInstance(superType.getClassLoader(), interfaces, dispatcher); + } + + protected Class[] getInterfaces(Class superType) { + if (superType.isInterface()) { + return new Class[] {superType}; + } else { + for (Annotation a : superType.getAnnotations()) { + Class annotationType = a.annotationType(); + if (isTypeAnnotation(annotationType)) { + try { + Class[] value = + (Class[]) annotationType.getMethod("value").invoke(a); + if (value.length == 0) { + value = superType.getInterfaces(); + } + List> nonInterfaces = + Stream.of(value).filter(c -> !c.isInterface()).toList(); + if (!nonInterfaces.isEmpty()) { + throw new IllegalArgumentException( + "The Typed annotation must contain only interfaces but the following types are not: " + + nonInterfaces); + } + return value; + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new IllegalStateException(e); + } + } + } + throw new IllegalArgumentException("The use of session scoped proxies require " + + "a org.eclipse.sisu.Typed or javax.enterprise.inject.Typed annotation"); + } + } + + protected boolean isTypeAnnotation(Class annotationType) { + return "org.apache.maven.api.di.Typed".equals(annotationType.getName()); + } + + /** + * A provider wrapping an existing provider with a cache + * @param the provided type + */ + protected static class CachingProvider implements Supplier { + private final Supplier provider; + private volatile T value; + + CachingProvider(Supplier provider) { + this.provider = provider; + } + + public T value() { + return value; + } + + @Override + public T get() { + if (value == null) { + synchronized (this) { + if (value == null) { + value = provider.get(); + } + } + } + return value; + } + } + + public static Supplier seededKeySupplier(Class clazz) { + return () -> { + throw new IllegalStateException("No instance of " + clazz.getName() + " is bound to the session scope."); + }; + } +} diff --git a/maven-core/src/main/java/org/apache/maven/execution/scope/internal/MojoExecutionScope.java b/maven-core/src/main/java/org/apache/maven/execution/scope/internal/MojoExecutionScope.java index a9d9feb65f3d..3b4bd83570c9 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/scope/internal/MojoExecutionScope.java +++ b/maven-core/src/main/java/org/apache/maven/execution/scope/internal/MojoExecutionScope.java @@ -18,20 +18,13 @@ */ package org.apache.maven.execution.scope.internal; -import java.lang.annotation.Annotation; import java.util.Collection; -import java.util.HashMap; import java.util.IdentityHashMap; -import java.util.LinkedList; -import java.util.Map; -import java.util.function.Supplier; import com.google.inject.Key; -import com.google.inject.OutOfScopeException; import com.google.inject.Provider; import com.google.inject.Scope; -import com.google.inject.name.Names; -import com.google.inject.util.Providers; +import com.google.inject.name.Named; import org.apache.maven.execution.MojoExecutionEvent; import org.apache.maven.execution.MojoExecutionListener; import org.apache.maven.execution.scope.WeakMojoExecutionListener; @@ -40,96 +33,22 @@ /** * MojoExecutionScope */ -public class MojoExecutionScope implements Scope, org.apache.maven.di.Scope, MojoExecutionListener { - private static final Provider SEEDED_KEY_PROVIDER = () -> { - throw new IllegalStateException(); - }; - - private static final class ScopeState { - private final Map, Provider> seeded = new HashMap<>(); - - private final Map, Object> provided = new HashMap<>(); - } - - private final ThreadLocal> values = new ThreadLocal<>(); - - public MojoExecutionScope() {} - - public void enter() { - LinkedList stack = values.get(); - if (stack == null) { - stack = new LinkedList<>(); - values.set(stack); - } - stack.addFirst(new ScopeState()); - } - - private ScopeState getScopeState() { - LinkedList stack = values.get(); - if (stack == null || stack.isEmpty()) { - throw new IllegalStateException(); - } - return stack.getFirst(); - } - - public void exit() throws MojoExecutionException { - final LinkedList stack = values.get(); - if (stack == null || stack.isEmpty()) { - throw new IllegalStateException(); - } - stack.removeFirst(); - if (stack.isEmpty()) { - values.remove(); - } - } +public class MojoExecutionScope extends org.apache.maven.internal.impl.di.MojoExecutionScope + implements Scope, MojoExecutionListener { public void seed(Class clazz, Provider value) { - getScopeState().seeded.put(Key.get(clazz), value); - } - - public void seed(Class clazz, final T value) { - getScopeState().seeded.put(Key.get(clazz), Providers.of(value)); - } - - public Provider scope(final Key key, final Provider unscoped) { - return () -> { - LinkedList stack = values.get(); - if (stack == null || stack.isEmpty()) { - throw new OutOfScopeException("Cannot access " + key + " outside of a scoping block"); - } - - ScopeState state = stack.getFirst(); - - Provider seeded = state.seeded.get(key); - - if (seeded != null) { - return (T) seeded.get(); - } - - T provided = (T) state.provided.get(key); - if (provided == null && unscoped != null) { - provided = unscoped.get(); - state.provided.put(key, provided); - } - - return provided; - }; + getScopeState().seed(clazz, value::get); } - @Override - public Supplier scope(org.apache.maven.di.Key key, Annotation scope, Supplier unscoped) { - Object qualifier = key.getQualifier(); - Key k = qualifier != null - ? Key.get(key.getType(), qualifier instanceof String s ? Names.named(s) : (Annotation) qualifier) - : Key.get(key.getType()); - Provider up = unscoped::get; - Provider p = scope(k, up); - return p::get; + public Provider scope(final Key key, Provider unscoped) { + Object qualifier = key.getAnnotation() instanceof Named n ? n.value() : key.getAnnotation(); + org.apache.maven.di.Key k = + org.apache.maven.di.Key.ofType(key.getTypeLiteral().getType(), qualifier); + return scope(k, unscoped::get)::get; } - @SuppressWarnings({"unchecked"}) - public static Provider seededKeyProvider() { - return (Provider) SEEDED_KEY_PROVIDER; + public static Provider seededKeyProvider(Class clazz) { + return MojoExecutionScope.seededKeySupplier(clazz)::get; } public void beforeMojoExecution(MojoExecutionEvent event) throws MojoExecutionException { @@ -154,7 +73,7 @@ private Collection getProvidedListeners() { // the same instance can be provided multiple times under different Key's // deduplicate instances to avoid redundant beforeXXX/afterXXX callbacks IdentityHashMap listeners = new IdentityHashMap<>(); - for (Object provided : getScopeState().provided.values()) { + for (Object provided : getScopeState().provided()) { if (provided instanceof WeakMojoExecutionListener) { listeners.put((WeakMojoExecutionListener) provided, null); } diff --git a/maven-core/src/main/java/org/apache/maven/execution/scope/internal/MojoExecutionScopeModule.java b/maven-core/src/main/java/org/apache/maven/execution/scope/internal/MojoExecutionScopeModule.java index 149b94f97c5d..a477ec868cbb 100644 --- a/maven-core/src/main/java/org/apache/maven/execution/scope/internal/MojoExecutionScopeModule.java +++ b/maven-core/src/main/java/org/apache/maven/execution/scope/internal/MojoExecutionScopeModule.java @@ -40,17 +40,19 @@ protected void configure() { // bindScope(org.apache.maven.api.di.MojoExecutionScoped.class, scope); bind(MojoExecutionScope.class).toInstance(scope); bind(MavenProject.class) - .toProvider(MojoExecutionScope.seededKeyProvider()) + .toProvider(MojoExecutionScope.seededKeyProvider(MavenProject.class)) .in(scope); bind(MojoExecution.class) - .toProvider(MojoExecutionScope.seededKeyProvider()) + .toProvider(MojoExecutionScope.seededKeyProvider(MojoExecution.class)) + .in(scope); + bind(Log.class) + .toProvider(MojoExecutionScope.seededKeyProvider(Log.class)) .in(scope); - bind(Log.class).toProvider(MojoExecutionScope.seededKeyProvider()).in(scope); bind(org.apache.maven.api.Project.class) - .toProvider(MojoExecutionScope.seededKeyProvider()) + .toProvider(MojoExecutionScope.seededKeyProvider(org.apache.maven.api.Project.class)) .in(scope); bind(org.apache.maven.api.MojoExecution.class) - .toProvider(MojoExecutionScope.seededKeyProvider()) + .toProvider(MojoExecutionScope.seededKeyProvider(org.apache.maven.api.MojoExecution.class)) .in(scope); } } diff --git a/maven-core/src/main/java/org/apache/maven/session/scope/internal/SessionScope.java b/maven-core/src/main/java/org/apache/maven/session/scope/internal/SessionScope.java index 284fafd6aea0..8098ec8b8c98 100644 --- a/maven-core/src/main/java/org/apache/maven/session/scope/internal/SessionScope.java +++ b/maven-core/src/main/java/org/apache/maven/session/scope/internal/SessionScope.java @@ -19,57 +19,31 @@ package org.apache.maven.session.scope.internal; import java.lang.annotation.Annotation; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CopyOnWriteArrayList; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import java.util.stream.Stream; import com.google.inject.Key; import com.google.inject.OutOfScopeException; import com.google.inject.Provider; import com.google.inject.Scope; -import com.google.inject.name.Names; +import com.google.inject.name.Named; /** * SessionScope */ -public class SessionScope implements Scope, org.apache.maven.di.Scope { +public class SessionScope extends org.apache.maven.internal.impl.di.SessionScope implements Scope { - private static final Provider SEEDED_KEY_PROVIDER = () -> { - throw new IllegalStateException(); - }; - - /** - * ScopeState - */ - protected static final class ScopeState { - private final Map, CachingProvider> provided = new ConcurrentHashMap<>(); - - public void seed(Class clazz, Provider value) { - provided.put(Key.get(clazz), new CachingProvider<>(value)); - } - - @SuppressWarnings("unchecked") - public Provider scope(Key key, Provider unscoped) { - Provider provider = provided.computeIfAbsent(key, k -> new CachingProvider<>(unscoped)); - return (Provider) provider; - } - - public Collection> providers() { - return provided.values(); - } + public void seed(Class clazz, Provider value) { + getScopeState().seed(clazz, value::get); } - private final List values = new CopyOnWriteArrayList<>(); + public Provider scope(final Key key, final Provider unscoped) { + Object qualifier = key.getAnnotation() instanceof Named n ? n.value() : key.getAnnotation(); + org.apache.maven.di.Key k = + org.apache.maven.di.Key.ofType(key.getTypeLiteral().getType(), qualifier); + return scope(k, unscoped::get)::get; + } - public void enter() { - values.add(0, new ScopeState()); + public static Provider seededKeyProvider(Class clazz) { + return SessionScope.seededKeySupplier(clazz)::get; } protected ScopeState getScopeState() { @@ -79,130 +53,10 @@ protected ScopeState getScopeState() { return values.get(0); } - public void exit() { - if (values.isEmpty()) { - throw new IllegalStateException(); - } - values.remove(0); - } - - public void seed(Class clazz, Provider value) { - getScopeState().seed(clazz, value); - } - - public void seed(Class clazz, final T value) { - seed(clazz, (Provider) () -> value); - } - - public Provider scope(final Key key, final Provider unscoped) { - // Lazy evaluating provider - return () -> { - if (values.isEmpty()) { - return createProxy(key, unscoped); - } else { - return getScopeState().scope(key, unscoped).get(); - } - }; - } - - @SuppressWarnings("unchecked") - @Override - public Supplier scope(org.apache.maven.di.Key key, Annotation scope, Supplier unscoped) { - Object qualifier = key.getQualifier(); - Key k = qualifier != null - ? Key.get(key.getType(), qualifier instanceof String s ? Names.named(s) : (Annotation) qualifier) - : Key.get(key.getType()); - Provider up = unscoped::get; - Provider p = scope((Key) k, up); - return p::get; - } - - @SuppressWarnings("unchecked") - private T createProxy(Key key, Provider unscoped) { - InvocationHandler dispatcher = (proxy, method, args) -> { - method.setAccessible(true); - try { - return method.invoke(getScopeState().scope(key, unscoped).get(), args); - } catch (InvocationTargetException e) { - throw e.getCause(); - } - }; - Class superType = (Class) key.getTypeLiteral().getRawType(); - Class[] interfaces = getInterfaces(superType); - return (T) java.lang.reflect.Proxy.newProxyInstance(superType.getClassLoader(), interfaces, dispatcher); - } - - private Class[] getInterfaces(Class superType) { - if (superType.isInterface()) { - return new Class[] {superType}; - } else { - for (Annotation a : superType.getAnnotations()) { - Class annotationType = a.annotationType(); - if ("org.apache.maven.api.di.Typed".equals(annotationType.getName()) - || "org.eclipse.sisu.Typed".equals(annotationType.getName()) - || "javax.enterprise.inject.Typed".equals(annotationType.getName()) - || "jakarta.enterprise.inject.Typed".equals(annotationType.getName())) { - try { - Class[] value = - (Class[]) annotationType.getMethod("value").invoke(a); - if (value.length == 0) { - value = superType.getInterfaces(); - } - List> nonInterfaces = - Stream.of(value).filter(c -> !c.isInterface()).collect(Collectors.toList()); - if (!nonInterfaces.isEmpty()) { - throw new IllegalArgumentException( - "The Typed annotation must contain only interfaces but the following types are not: " - + nonInterfaces); - } - return value; - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - throw new IllegalStateException(e); - } - } - } - throw new IllegalArgumentException("The use of session scoped proxies require " - + "a org.eclipse.sisu.Typed or javax.enterprise.inject.Typed annotation"); - } - } - - /** - * A provider wrapping an existing provider with a cache - * @param the provided type - */ - protected static class CachingProvider implements Provider { - private final Provider provider; - private volatile T value; - - CachingProvider(Provider provider) { - this.provider = provider; - } - - public T value() { - return value; - } - - @Override - public T get() { - if (value == null) { - synchronized (this) { - if (value == null) { - value = provider.get(); - } - } - } - return value; - } - } - - @SuppressWarnings({"unchecked"}) - public static Provider seededKeyProvider() { - return (Provider) SEEDED_KEY_PROVIDER; - } - - public static Provider seededKeyProvider(Class clazz) { - return () -> { - throw new IllegalStateException("No instance of " + clazz.getName() + " is bound to the session scope."); - }; + protected boolean isTypeAnnotation(Class annotationType) { + return "org.apache.maven.api.di.Typed".equals(annotationType.getName()) + || "org.eclipse.sisu.Typed".equals(annotationType.getName()) + || "javax.enterprise.inject.Typed".equals(annotationType.getName()) + || "jakarta.enterprise.inject.Typed".equals(annotationType.getName()); } } diff --git a/maven-core/src/test/java/org/apache/maven/execution/scope/internal/MojoExecutionScopeTest.java b/maven-core/src/test/java/org/apache/maven/execution/scope/internal/MojoExecutionScopeTest.java index 5e53789a47f1..09af78fb6e58 100644 --- a/maven-core/src/test/java/org/apache/maven/execution/scope/internal/MojoExecutionScopeTest.java +++ b/maven-core/src/test/java/org/apache/maven/execution/scope/internal/MojoExecutionScopeTest.java @@ -39,15 +39,15 @@ void testNestedEnter() throws Exception { Object o1 = new Object(); scope.seed(Object.class, o1); - assertSame(o1, scope.scope(Key.get(Object.class), null).get()); + assertSame(o1, scope.scope(Key.get(Object.class), () -> null).get()); scope.enter(); Object o2 = new Object(); scope.seed(Object.class, o2); - assertSame(o2, scope.scope(Key.get(Object.class), null).get()); + assertSame(o2, scope.scope(Key.get(Object.class), () -> null).get()); scope.exit(); - assertSame(o1, scope.scope(Key.get(Object.class), null).get()); + assertSame(o1, scope.scope(Key.get(Object.class), () -> null).get()); scope.exit(); 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 d4eea9862526..56b2212650d2 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 @@ -36,27 +36,29 @@ static Injector create() { } @Nonnull - Injector discover(ClassLoader classLoader); + Injector discover(@Nonnull ClassLoader classLoader); @Nonnull - Injector bindScope(Class scopeAnnotation, Scope scope); + Injector bindScope(@Nonnull Class scopeAnnotation, @Nonnull Scope scope); @Nonnull - Injector bindScope(Class scopeAnnotation, Supplier scope); + Injector bindScope(@Nonnull Class scopeAnnotation, @Nonnull Supplier scope); @Nonnull - Injector bindImplicit(Class cls); + Injector bindImplicit(@Nonnull Class cls); @Nonnull - Injector bindInstance(Class cls, T instance); + Injector bindInstance(@Nonnull Class cls, @Nonnull T instance); // // Bean access // - void injectInstance(T instance); + void injectInstance(@Nonnull T instance); - T getInstance(Class key); + @Nonnull + T getInstance(@Nonnull Class key); - T getInstance(Key key); + @Nonnull + T getInstance(@Nonnull Key key); } diff --git a/maven-di/src/main/java/org/apache/maven/di/Key.java b/maven-di/src/main/java/org/apache/maven/di/Key.java index 3e922bf0a4f5..6f13be353107 100644 --- a/maven-di/src/main/java/org/apache/maven/di/Key.java +++ b/maven-di/src/main/java/org/apache/maven/di/Key.java @@ -31,7 +31,7 @@ * The key defines an identity of a binding. In any DI, a key is usually a type of the object along * with some optional tag to distinguish between bindings which make objects of the same type. *

- * In ActiveJ Inject, a key is also a type token - special abstract class that can store type information + * In Maven Inject, a key is also a type token - special abstract class that can store type information * with the shortest syntax possible in Java. *

* For example, to create a key of type Map<String, List<Integer>>, you can just use diff --git a/maven-di/src/main/java/org/apache/maven/di/Scope.java b/maven-di/src/main/java/org/apache/maven/di/Scope.java index c05334f319f2..5da978b64a8f 100644 --- a/maven-di/src/main/java/org/apache/maven/di/Scope.java +++ b/maven-di/src/main/java/org/apache/maven/di/Scope.java @@ -18,10 +18,34 @@ */ package org.apache.maven.di; -import java.lang.annotation.Annotation; import java.util.function.Supplier; +import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.annotations.Nonnull; + +/** + * A {@code Scope} defines how visible instances are when managed by a {@link org.apache.maven.di.Injector}. + * Typically, instances are created with no scope, meaning they don’t retain any state from the + * framework’s perspective: the {@code Injector} generates the instance, injects it into the necessary class, + * and then immediately forgets it. By linking a scope to a specific binding, the created instance can be + * “remembered” and reused for future injections. + *

+ * Instances are associated to a given scope by means of a {@link org.apache.maven.api.di.Scope @Scope} + * annotation, usually put on another annotation. For example, the {@code @Singleton} annotation is used + * to indicate that a given binding should be scoped as a singleton. + *

+ * The following scopes are currently supported: + *

    + *
  • {@link org.apache.maven.api.di.Singleton @Singleton}
  • + *
  • {@link org.apache.maven.api.di.SessionScoped @SessionScoped}
  • + *
  • {@link org.apache.maven.api.di.MojoExecutionScoped @MojoExecutionScoped}
  • + *
+ * + * @since 4.0.0 + */ +@Experimental public interface Scope { - Supplier scope(Key key, Annotation scope, Supplier unscoped); + @Nonnull + Supplier scope(@Nonnull Key key, @Nonnull Supplier unscoped); } diff --git a/maven-di/src/main/java/org/apache/maven/di/impl/DIException.java b/maven-di/src/main/java/org/apache/maven/di/impl/DIException.java index 4aca38c16e97..b5e39bbfe792 100644 --- a/maven-di/src/main/java/org/apache/maven/di/impl/DIException.java +++ b/maven-di/src/main/java/org/apache/maven/di/impl/DIException.java @@ -25,7 +25,7 @@ * (missing or cyclic dependencies, incorrect annotations etc.) or in runtime when * you ask an {@link Injector} for an instance it does not have a {@link Binding binding} for. */ -public final class DIException extends RuntimeException { +public class DIException extends RuntimeException { public DIException(String message) { super(message); } 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 1e047f55c211..34508ce18b20 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 @@ -241,7 +241,7 @@ protected Supplier compile(Binding binding) { .orElseThrow(() -> new DIException("Scope not bound for annotation " + binding.getScope().annotationType())) .get(); - compiled = scope.scope((Key) binding.getOriginalKey(), binding.getScope(), compiled); + compiled = scope.scope((Key) binding.getOriginalKey(), compiled); } return compiled; } @@ -379,8 +379,7 @@ private static class SingletonScope implements Scope { @SuppressWarnings("unchecked") @Override - public java.util.function.Supplier scope( - Key key, Annotation scope, java.util.function.Supplier unscoped) { + public java.util.function.Supplier scope(Key key, java.util.function.Supplier unscoped) { return (java.util.function.Supplier) cache.computeIfAbsent(key, k -> new java.util.function.Supplier() { volatile T instance;