From f7647a9f249a5600b52ea0cbdb6abce7f3ab335a Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Wed, 7 Feb 2024 08:57:50 +0100 Subject: [PATCH 1/3] [DI] Fix constructor bindings, move discovery inside Injector, remove annotation processor --- .../internal/DefaultMavenPluginManager.java | 11 +- .../java/org/apache/maven/di/Injector.java | 2 + .../main/java/org/apache/maven/di/Key.java | 5 +- .../org/apache/maven/di/impl/Binding.java | 27 +- .../apache/maven/di/impl/InjectorImpl.java | 164 ++++---- .../apache/maven/di/impl/ReflectionUtils.java | 52 +-- .../org/apache/maven/di/impl/TypeUtils.java | 379 ------------------ .../java/org/apache/maven/di/impl/Types.java | 239 ++++++++++- .../processor/IndexAnnotationProcessor.java | 152 ------- .../javax.annotation.processing.Processor | 1 - .../{DITest.java => InjectorImplTest.java} | 46 ++- .../java/org/apache/maven/di/impl/TypeT.java | 83 ---- .../apache/maven/di/impl/TypeUtilsTest.java | 87 ++-- 13 files changed, 450 insertions(+), 798 deletions(-) delete mode 100644 maven-di/src/main/java/org/apache/maven/di/impl/TypeUtils.java delete mode 100644 maven-di/src/main/java/org/apache/maven/di/processor/IndexAnnotationProcessor.java delete mode 100644 maven-di/src/main/resources/META-INF/services/javax.annotation.processing.Processor rename maven-di/src/test/java/org/apache/maven/di/impl/{DITest.java => InjectorImplTest.java} (88%) delete mode 100644 maven-di/src/test/java/org/apache/maven/di/impl/TypeT.java diff --git a/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java b/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java index 7292c8c94231..cf74e61f757c 100644 --- a/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java +++ b/maven-core/src/main/java/org/apache/maven/plugin/internal/DefaultMavenPluginManager.java @@ -526,23 +526,14 @@ private T loadV4Mojo( org.apache.maven.api.plugin.Log log = new DefaultLog( LoggerFactory.getLogger(mojoExecution.getMojoDescriptor().getFullGoalName())); try { - Set classes = new HashSet<>(); - try (InputStream is = pluginRealm.getResourceAsStream("META-INF/maven/org.apache.maven.api.di.Inject"); - BufferedReader reader = new BufferedReader(new InputStreamReader(Objects.requireNonNull(is)))) { - reader.lines().forEach(classes::add); - } Injector injector = Injector.create(); + injector.discover(pluginRealm); // Add known classes // TODO: get those from the existing plexus scopes ? injector.bindInstance(Session.class, sessionV4); injector.bindInstance(Project.class, project); injector.bindInstance(org.apache.maven.api.MojoExecution.class, execution); injector.bindInstance(org.apache.maven.api.plugin.Log.class, log); - // Add plugin classes - for (String className : classes) { - Class clazz = pluginRealm.loadClass(className); - injector.bindImplicit(clazz); - } mojo = mojoInterface.cast(injector.getInstance(mojoDescriptor.getImplementationClass())); } catch (Exception e) { 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 9e9b65a33f2a..3b2d56d204da 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 @@ -32,6 +32,8 @@ static Injector create() { return new InjectorImpl(); } + Injector discover(ClassLoader classLoader); + Injector bindScope(Class scopeAnnotation, Scope scope); Injector bindImplicit(Class cls); 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 cdf9bc040c7d..d27882ae76cb 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 @@ -24,7 +24,6 @@ import org.apache.maven.api.annotations.Nullable; import org.apache.maven.di.impl.ReflectionUtils; -import org.apache.maven.di.impl.TypeUtils; import org.apache.maven.di.impl.Types; import org.apache.maven.di.impl.Utils; @@ -54,12 +53,12 @@ protected Key() { } protected Key(@Nullable Object qualifier) { - this.type = TypeUtils.simplifyType(getTypeParameter()); + this.type = Types.simplifyType(getTypeParameter()); this.qualifier = qualifier; } protected Key(Type type, @Nullable Object qualifier) { - this.type = TypeUtils.simplifyType(type); + this.type = Types.simplifyType(type); this.qualifier = qualifier; } 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 fc46f84619f0..1f74a6721acc 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 @@ -19,10 +19,7 @@ package org.apache.maven.di.impl; import java.lang.annotation.Annotation; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; +import java.util.*; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; @@ -54,16 +51,18 @@ public static Binding toInstance(T instance) { return new BindingToInstance<>(instance); } - public static Binding to(TupleConstructorN constructor, Class[] types) { - return Binding.to(constructor, Stream.of(types).map(Key::of).toArray(Key[]::new)); + public static Binding to(Key originalKey, TupleConstructorN constructor, Class[] types) { + return Binding.to( + originalKey, constructor, Stream.of(types).map(Key::of).toArray(Key[]::new)); } - public static Binding to(TupleConstructorN constructor, Key[] dependencies) { - return to(constructor, dependencies, 0); + public static Binding to(Key originalKey, TupleConstructorN constructor, Key[] dependencies) { + return to(originalKey, constructor, dependencies, 0); } - public static Binding to(TupleConstructorN constructor, Key[] dependencies, int priority) { - return new BindingToConstructor<>(null, constructor, dependencies, priority); + public static Binding to( + Key originalKey, TupleConstructorN constructor, Key[] dependencies, int priority) { + return new BindingToConstructor<>(originalKey, constructor, dependencies, priority); } // endregion @@ -162,20 +161,20 @@ public String toString() { public static class BindingToConstructor extends Binding { final TupleConstructorN constructor; + final Key[] args; BindingToConstructor( Key key, TupleConstructorN constructor, Key[] 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) { return () -> { - Object[] args = getDependencies().stream() - .map(compiler) - .map(Supplier::get) - .toArray(); + Object[] args = + Stream.of(this.args).map(compiler).map(Supplier::get).toArray(); return constructor.create(args); }; } 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 49df5788484d..a2ad2ca6f1cb 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 @@ -18,16 +18,21 @@ */ package org.apache.maven.di.impl; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; +import java.net.URL; import java.util.*; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.maven.api.di.Provides; +import org.apache.maven.api.di.Qualifier; import org.apache.maven.api.di.Singleton; import org.apache.maven.api.di.Typed; import org.apache.maven.di.Injector; @@ -59,6 +64,26 @@ public void injectInstance(T instance) { .accept(instance); } + @Override + public Injector discover(ClassLoader classLoader) { + try { + Enumeration enumeration = classLoader.getResources("META-INF/maven/org.apache.maven.api.di.Inject"); + while (enumeration.hasMoreElements()) { + 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())) { + Class clazz = classLoader.loadClass(line); + bindImplicit(clazz); + } + } + } + } catch (Exception e) { + throw new DIException("Error while discovering DI classes from classLoader", e); + } + return this; + } + public Injector bindScope(Class scopeAnnotation, Scope scope) { if (scopes.put(scopeAnnotation, scope) != null) { throw new DIException( @@ -80,7 +105,13 @@ public Injector bindImplicit(Class clazz) { return doBind(key, binding); } + private LinkedHashSet> current = new LinkedHashSet<>(); + private Injector doBind(Key key, Binding binding) { + if (!current.add(key)) { + current.add(key); + throw new DIException("Circular references: " + current); + } doBindImplicit(key, binding); Class cls = key.getRawType().getSuperclass(); while (cls != Object.class && cls != null) { @@ -88,6 +119,7 @@ private Injector doBind(Key key, Binding binding) { doBindImplicit(key, binding); cls = cls.getSuperclass(); } + current.remove(key); return this; } @@ -157,115 +189,61 @@ private Supplier compile(Binding binding) { protected void doBindImplicit(Key key, Binding binding) { if (binding != null) { // For non-explicit bindings, also bind all their base classes and interfaces according to the @Type - Set> toBind = new HashSet<>(); - Deque> todo = new ArrayDeque<>(); - todo.add(key); - - Set> types; - Typed typed = key.getRawType().getAnnotation(Typed.class); - if (typed != null) { - Class[] typesArray = typed.value(); - if (typesArray == null || typesArray.length == 0) { - types = new HashSet<>(Arrays.asList(key.getRawType().getInterfaces())); - types.add(Object.class); - } else { - types = new HashSet<>(Arrays.asList(typesArray)); - } - } else { - types = null; - } - - Set> done = new HashSet<>(); - while (!todo.isEmpty()) { - Key type = todo.remove(); - if (done.add(type)) { - Class cls = Types.getRawType(type.getType()); - Type[] interfaces = cls.getGenericInterfaces(); - Arrays.stream(interfaces) - .map(t -> Key.ofType(t, key.getQualifier())) - .forEach(todo::add); - Type supercls = cls.getGenericSuperclass(); - if (supercls != null) { - todo.add(Key.ofType(supercls, key.getQualifier())); - } - if (types == null || types.contains(cls)) { - toBind.add(type); + Object qualifier = key.getQualifier(); + Class type = key.getRawType(); + Set> types = getBoundTypes(type.getAnnotation(Typed.class), type); + for (Type t : Types.getAllSuperTypes(type)) { + if (types == null || types.contains(Types.getRawType(t))) { + bind(Key.ofType(t, qualifier), binding); + if (qualifier != null) { + bind(Key.ofType(t), binding); } } } - // Also bind without the qualifier - if (key.getQualifier() != null) { - new HashSet<>(toBind).forEach(k -> toBind.add(Key.ofType(k.getType()))); - } - toBind.forEach((k -> bind((Key) k, (Binding) binding))); } // Bind inner classes for (Class inner : key.getRawType().getDeclaredClasses()) { - bindImplicit(inner); + boolean hasQualifier = Stream.of(inner.getAnnotations()) + .anyMatch(ann -> ann.annotationType().isAnnotationPresent(Qualifier.class)); + if (hasQualifier) { + bindImplicit(inner); + } } // Bind inner providers for (Method method : key.getRawType().getDeclaredMethods()) { if (method.isAnnotationPresent(Provides.class)) { - Object qualifier = ReflectionUtils.qualifierOf(method); - Annotation scope = ReflectionUtils.scopeOf(method); - - TypeVariable[] methodTypeParameters = method.getTypeParameters(); - if (methodTypeParameters.length != 0) { + if (method.getTypeParameters().length != 0) { throw new DIException("Parameterized method are not supported " + method); } - Map, Type> mapping = new HashMap<>(); - for (TypeVariable methodTypeParameter : methodTypeParameters) { - mapping.put(methodTypeParameter, methodTypeParameter); - } - mapping.putAll(Types.getAllTypeBindings(key.getRawType())); - - Type returnType = Types.bind(method.getGenericReturnType(), mapping); - Key rkey = Key.ofType(returnType, qualifier); - - Set> types; - Typed typed = method.getAnnotation(Typed.class); - if (typed != null) { - Class[] typesArray = typed.value(); - if (typesArray == null || typesArray.length == 0) { - types = new HashSet<>(Arrays.asList(rkey.getRawType().getInterfaces())); - types.add(Object.class); - } else { - types = new HashSet<>(Arrays.asList(typesArray)); - } - } else { - types = null; - } - - Set> toBind = new HashSet<>(); - Deque> todo = new ArrayDeque<>(); - todo.add(rkey); - - Set> done = new HashSet<>(); - while (!todo.isEmpty()) { - Key type = todo.remove(); - if (done.add(type)) { - Class cls = Types.getRawType(type.getType()); - Type[] interfaces = cls.getGenericInterfaces(); - Arrays.stream(interfaces) - .map(t -> Key.ofType(t, qualifier)) - .forEach(todo::add); - Type supercls = cls.getGenericSuperclass(); - if (supercls != null) { - todo.add(Key.ofType(supercls, qualifier)); - } - if (types == null || types.contains(cls)) { - toBind.add(type); + Object qualifier = ReflectionUtils.qualifierOf(method); + Annotation scope = ReflectionUtils.scopeOf(method); + Type returnType = method.getGenericReturnType(); + Set> types = getBoundTypes(method.getAnnotation(Typed.class), Types.getRawType(returnType)); + Binding bind = ReflectionUtils.bindingFromMethod(method).scope(scope); + for (Type t : Types.getAllSuperTypes(returnType)) { + if (types == null || types.contains(Types.getRawType(t))) { + bind(Key.ofType(t, qualifier), bind); + if (qualifier != null) { + bind(Key.ofType(t), bind); } } } - // Also bind without the qualifier - if (qualifier != null) { - new HashSet<>(toBind).forEach(k -> toBind.add(Key.ofType(k.getType()))); - } + } + } + } - Binding bind = ReflectionUtils.bindingFromMethod(method).scope(scope); - toBind.forEach((k -> bind((Key) k, bind))); + private static Set> getBoundTypes(Typed typed, Class clazz) { + if (typed != null) { + Class[] typesArray = typed.value(); + if (typesArray == null || typesArray.length == 0) { + Set> types = new HashSet<>(Arrays.asList(clazz.getInterfaces())); + types.add(Object.class); + return types; + } else { + return new HashSet<>(Arrays.asList(typesArray)); } + } else { + return null; } } 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 dae002f22fda..93ec8767dc3a 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 @@ -152,10 +152,6 @@ public static List getAnnotatedElements public static @Nullable Binding generateConstructorBinding(Key key) { Class cls = key.getRawType(); - Annotation classInjectAnnotation = Stream.of(cls.getAnnotations()) - .filter(a -> a.annotationType().isAnnotationPresent(Qualifier.class)) - .findAny() - .orElse(null); List> constructors = Arrays.asList(cls.getDeclaredConstructors()); List> injectConstructors = constructors.stream() .filter(c -> c.isAnnotationPresent(Inject.class)) @@ -168,32 +164,6 @@ public static List getAnnotatedElements .filter(method -> method.isAnnotationPresent(Inject.class)) .collect(toList()); - if (classInjectAnnotation != null) { - if (!injectConstructors.isEmpty()) { - throw failedImplicitBinding(key, "inject annotation on class with inject constructor"); - } - if (!factoryMethods.isEmpty()) { - throw failedImplicitBinding(key, "inject annotation on class with factory method"); - } - if (constructors.isEmpty()) { - throw failedImplicitBinding(key, "inject annotation on interface"); - } - if (constructors.size() > 1) { - throw failedImplicitBinding(key, "inject annotation on class with multiple constructors"); - } - Constructor declaredConstructor = - (Constructor) constructors.iterator().next(); - - Class enclosingClass = cls.getEnclosingClass(); - if (enclosingClass != null - && !Modifier.isStatic(cls.getModifiers()) - && declaredConstructor.getParameterCount() != 1) { - throw failedImplicitBinding( - key, - "inject annotation on local class that closes over outside variables and/or has no default constructor"); - } - return bindingFromConstructor(key, declaredConstructor); - } if (!injectConstructors.isEmpty()) { if (injectConstructors.size() > 1) { throw failedImplicitBinding(key, "more than one inject constructor"); @@ -211,7 +181,25 @@ public static List getAnnotatedElements } return bindingFromMethod(injectFactoryMethods.iterator().next()); } - return null; + + if (constructors.isEmpty()) { + throw failedImplicitBinding(key, "inject annotation on interface"); + } + if (constructors.size() > 1) { + throw failedImplicitBinding(key, "inject annotation on class with multiple constructors"); + } + Constructor declaredConstructor = + (Constructor) constructors.iterator().next(); + + Class enclosingClass = cls.getEnclosingClass(); + if (enclosingClass != null + && !Modifier.isStatic(cls.getModifiers()) + && declaredConstructor.getParameterCount() != 1) { + throw failedImplicitBinding( + key, + "inject annotation on local class that closes over outside variables and/or has no default constructor"); + } + return bindingFromConstructor(key, declaredConstructor); } private static DIException failedImplicitBinding(Key requestedKey, String message) { @@ -312,6 +300,7 @@ private static Key[] toArgDependencies(@Nullable Type container, Executable e public static Binding bindingFromMethod(Method method) { method.setAccessible(true); Binding binding = Binding.to( + Key.ofType(method.getGenericReturnType(), ReflectionUtils.qualifierOf(method)), args -> { try { Object instance; @@ -351,6 +340,7 @@ public static Binding bindingFromConstructor(Key key, Constructor c Key[] dependencies = toDependencies(key.getType(), constructor); Binding binding = Binding.to( + key, args -> { try { return constructor.newInstance(args); diff --git a/maven-di/src/main/java/org/apache/maven/di/impl/TypeUtils.java b/maven-di/src/main/java/org/apache/maven/di/impl/TypeUtils.java deleted file mode 100644 index dbd8b72578e5..000000000000 --- a/maven-di/src/main/java/org/apache/maven/di/impl/TypeUtils.java +++ /dev/null @@ -1,379 +0,0 @@ -/* - * 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 java.lang.reflect.*; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; - -/** - * This class contains reflection utilities to work with Java types. - * Its main use is for method {@link Types#parameterizedType Types.parameterized}. - * However, just like with {@link ReflectionUtils}, other type utility - * methods are pretty clean too, so they are left public. - */ -public final class TypeUtils { - - public static boolean isInheritedFrom(Type type, Type from, Map dejaVu) { - if (from == Object.class) { - return true; - } - if (matches(type, from, dejaVu) || matches(from, type, dejaVu)) { - return true; - } - if (!(type instanceof Class || type instanceof ParameterizedType || type instanceof GenericArrayType)) { - return false; - } - Class rawType = Types.getRawType(type); - - Type superclass = rawType.getGenericSuperclass(); - if (superclass != null && isInheritedFrom(superclass, from, dejaVu)) { - return true; - } - return Arrays.stream(rawType.getGenericInterfaces()).anyMatch(iface -> isInheritedFrom(iface, from, dejaVu)); - } - - public static boolean matches(Type strict, Type pattern) { - return matches(strict, pattern, new HashMap<>()); - } - - private static boolean matches(Type strict, Type pattern, Map dejaVu) { - if (strict.equals(pattern) || dejaVu.get(strict) == pattern) { - return true; - } - dejaVu.put(strict, pattern); - try { - if (pattern instanceof WildcardType) { - WildcardType wildcard = (WildcardType) pattern; - return Arrays.stream(wildcard.getUpperBounds()) - .allMatch(bound -> isInheritedFrom(strict, bound, dejaVu)) - && Arrays.stream(wildcard.getLowerBounds()) - .allMatch(bound -> isInheritedFrom(bound, strict, dejaVu)); - } - if (pattern instanceof TypeVariable) { - TypeVariable typevar = (TypeVariable) pattern; - return Arrays.stream(typevar.getBounds()).allMatch(bound -> isInheritedFrom(strict, bound, dejaVu)); - } - if (strict instanceof GenericArrayType && pattern instanceof GenericArrayType) { - return matches( - ((GenericArrayType) strict).getGenericComponentType(), - ((GenericArrayType) pattern).getGenericComponentType(), - dejaVu); - } - if (!(strict instanceof ParameterizedType) || !(pattern instanceof ParameterizedType)) { - return false; - } - ParameterizedType parameterizedStrict = (ParameterizedType) strict; - ParameterizedType parameterizedPattern = (ParameterizedType) pattern; - if (parameterizedPattern.getOwnerType() != null) { - if (parameterizedStrict.getOwnerType() == null) { - return false; - } - if (!matches(parameterizedPattern.getOwnerType(), parameterizedStrict.getOwnerType(), dejaVu)) { - return false; - } - } - if (!matches(parameterizedPattern.getRawType(), parameterizedStrict.getRawType(), dejaVu)) { - return false; - } - - Type[] strictParams = parameterizedStrict.getActualTypeArguments(); - Type[] patternParams = parameterizedPattern.getActualTypeArguments(); - if (strictParams.length != patternParams.length) { - return false; - } - for (int i = 0; i < strictParams.length; i++) { - if (!matches(strictParams[i], patternParams[i], dejaVu)) { - return false; - } - } - return true; - } finally { - dejaVu.remove(strict); - } - } - - public static boolean contains(Type type, Type sub) { - if (type.equals(sub)) { - return true; - } - if (type instanceof GenericArrayType) { - return contains(((GenericArrayType) type).getGenericComponentType(), sub); - } - if (!(type instanceof ParameterizedType)) { - return false; - } - ParameterizedType parameterized = (ParameterizedType) type; - if (contains(parameterized.getRawType(), sub)) { - return true; - } - if (parameterized.getOwnerType() != null && contains(parameterized.getOwnerType(), sub)) { - return true; - } - return Arrays.stream(parameterized.getActualTypeArguments()).anyMatch(argument -> contains(argument, sub)); - } - - // pattern = Map> - // real = Map> - // - // result = {K -> String, V -> Integer} - public static Map, Type> extractMatchingGenerics(Type pattern, Type real) { - Map, Type> result = new HashMap<>(); - extractMatchingGenerics(pattern, real, result); - return result; - } - - private static void extractMatchingGenerics(Type pattern, Type real, Map, Type> result) { - if (pattern instanceof TypeVariable) { - result.put((TypeVariable) pattern, real); - return; - } - if (pattern.equals(real)) { - return; - } - if (pattern instanceof GenericArrayType && real instanceof GenericArrayType) { - extractMatchingGenerics( - ((GenericArrayType) pattern).getGenericComponentType(), - ((GenericArrayType) real).getGenericComponentType(), - result); - return; - } - if (!(pattern instanceof ParameterizedType) || !(real instanceof ParameterizedType)) { - return; - } - ParameterizedType parameterizedPattern = (ParameterizedType) pattern; - ParameterizedType parameterizedReal = (ParameterizedType) real; - if (!parameterizedPattern.getRawType().equals(parameterizedReal.getRawType())) { - return; - } - extractMatchingGenerics(parameterizedPattern.getRawType(), parameterizedReal.getRawType(), result); - if (!Objects.equals(parameterizedPattern.getOwnerType(), parameterizedReal.getOwnerType())) { - return; - } - if (parameterizedPattern.getOwnerType() != null) { - extractMatchingGenerics(parameterizedPattern.getOwnerType(), parameterizedReal.getOwnerType(), result); - } - Type[] patternTypeArgs = parameterizedPattern.getActualTypeArguments(); - Type[] realTypeArgs = parameterizedReal.getActualTypeArguments(); - if (patternTypeArgs.length != realTypeArgs.length) { - return; - } - for (int i = 0; i < patternTypeArgs.length; i++) { - extractMatchingGenerics(patternTypeArgs[i], realTypeArgs[i], result); - } - } - - public static Type simplifyType(Type original) { - if (original instanceof Class) { - return original; - } - - if (original instanceof GenericArrayType) { - Type componentType = ((GenericArrayType) original).getGenericComponentType(); - Type repackedComponentType = simplifyType(componentType); - if (componentType != repackedComponentType) { - return Types.genericArrayType(repackedComponentType); - } - return original; - } - - if (original instanceof ParameterizedType) { - ParameterizedType parameterizedType = (ParameterizedType) original; - Type[] typeArguments = parameterizedType.getActualTypeArguments(); - Type[] repackedTypeArguments = simplifyTypes(typeArguments); - - if (isAllObjects(repackedTypeArguments)) { - return parameterizedType.getRawType(); - } - - if (typeArguments != repackedTypeArguments) { - return Types.parameterizedType( - parameterizedType.getOwnerType(), parameterizedType.getRawType(), repackedTypeArguments); - } - return original; - } - - if (original instanceof TypeVariable) { - throw new IllegalArgumentException("Key should not contain a type variable: " + original); - } - - if (original instanceof WildcardType) { - WildcardType wildcardType = (WildcardType) original; - Type[] upperBounds = wildcardType.getUpperBounds(); - if (upperBounds.length == 1) { - Type upperBound = upperBounds[0]; - if (upperBound != Object.class) { - return simplifyType(upperBound); - } - } else if (upperBounds.length > 1) { - throw new IllegalArgumentException("Multiple upper bounds not supported: " + original); - } - - Type[] lowerBounds = wildcardType.getLowerBounds(); - if (lowerBounds.length == 1) { - return simplifyType(lowerBounds[0]); - } else if (lowerBounds.length > 1) { - throw new IllegalArgumentException("Multiple lower bounds not supported: " + original); - } - return Object.class; - } - - return original; - } - - private static Type[] simplifyTypes(Type[] original) { - int length = original.length; - for (int i = 0; i < length; i++) { - Type typeArgument = original[i]; - Type repackTypeArgument = simplifyType(typeArgument); - if (repackTypeArgument != typeArgument) { - Type[] repackedTypeArguments = new Type[length]; - System.arraycopy(original, 0, repackedTypeArguments, 0, i); - repackedTypeArguments[i++] = repackTypeArgument; - for (; i < length; i++) { - repackedTypeArguments[i] = simplifyType(original[i]); - } - return repackedTypeArguments; - } - } - return original; - } - - private static boolean isAllObjects(Type[] types) { - for (Type type : types) { - if (type != Object.class) { - return false; - } - } - return true; - } - - /** - * Tests whether a {@code from} type is assignable to {@code to} type - * - * @param to a 'to' type that should be checked for possible assignment - * @param from a 'from' type that should be checked for possible assignment - * @return whether an object of type {@code from} is assignable to an object of type {@code to} - */ - public static boolean isAssignable(Type to, Type from) { - // shortcut - if (to instanceof Class && from instanceof Class) { - return ((Class) to).isAssignableFrom((Class) from); - } - return isAssignable(to, from, false); - } - - 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; - toUppers = wildcardTo.getUpperBounds(); - toLowers = wildcardTo.getLowerBounds(); - } else { - toUppers = new Type[] {to}; - toLowers = strict ? toUppers : Types.NO_TYPES; - } - - Type[] fromUppers, fromLowers; - if (from instanceof WildcardType) { - WildcardType wildcardTo = (WildcardType) to; - fromUppers = wildcardTo.getUpperBounds(); - fromLowers = wildcardTo.getLowerBounds(); - } else { - fromUppers = new Type[] {from}; - fromLowers = strict ? fromUppers : Types.NO_TYPES; - } - - for (Type toUpper : toUppers) { - for (Type fromUpper : fromUppers) { - if (!isAssignable(toUpper, fromUpper, false)) { - return false; - } - } - } - if (toLowers.length == 0) { - return true; - } - if (fromLowers.length == 0) { - return false; - } - for (Type toLower : toLowers) { - for (Type fromLower : fromLowers) { - if (!isAssignable(fromLower, toLower, false)) { - return false; - } - } - } - return true; - } - if (to instanceof GenericArrayType) { - to = Types.getRawType(to); - } - if (from instanceof GenericArrayType) { - from = Types.getRawType(from); - } - if (!strict && to instanceof Class) { - return ((Class) to).isAssignableFrom(Types.getRawType(from)); - } - Class toRawClazz = Types.getRawType(to); - Type[] toTypeArguments = Types.getActualTypeArguments(to); - return isAssignable(toRawClazz, toTypeArguments, from, strict); - } - - private static boolean isAssignable(Class toRawClazz, Type[] toTypeArguments, Type from, boolean strict) { - Class fromRawClazz = Types.getRawType(from); - if (strict && !toRawClazz.equals(fromRawClazz)) { - return false; - } - if (!strict && !toRawClazz.isAssignableFrom(fromRawClazz)) { - return false; - } - if (toRawClazz.isArray()) { - return true; - } - Type[] fromTypeArguments = Types.getActualTypeArguments(from); - if (toRawClazz == fromRawClazz) { - if (toTypeArguments.length > fromTypeArguments.length) { - return false; - } - for (int i = 0; i < toTypeArguments.length; i++) { - if (!isAssignable(toTypeArguments[i], fromTypeArguments[i], true)) { - return false; - } - } - return true; - } - Map, Type> typeBindings = Types.getTypeBindings(from); - for (Type anInterface : fromRawClazz.getGenericInterfaces()) { - if (isAssignable( - toRawClazz, - toTypeArguments, - Types.bind(anInterface, key -> typeBindings.getOrDefault(key, Types.wildcardTypeAny())), - false)) { - return true; - } - } - Type superclass = fromRawClazz.getGenericSuperclass(); - return superclass != null - && isAssignable(toRawClazz, toTypeArguments, Types.bind(superclass, typeBindings), false); - } -} 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 4fc8a7661f24..556fafb493d6 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 @@ -70,10 +70,10 @@ public static Type getUppermostType(Type[] types) { Type result = types[0]; for (int i = 1; i < types.length; i++) { Type type = types[i]; - if (TypeUtils.isAssignable(type, result)) { + if (isAssignable(type, result)) { result = type; continue; - } else if (TypeUtils.isAssignable(result, type)) { + } else if (isAssignable(result, type)) { continue; } throw new IllegalArgumentException("Unrelated types: " + result + " , " + type); @@ -235,6 +235,241 @@ public static ParameterizedType parameterizedType(Class rawType, Type... para return new ParameterizedTypeImpl(null, rawType, parameters); } + /** + * Get all super classes and interface implemented by the given type. + */ + public static Set getAllSuperTypes(Type original) { + Deque todo = new ArrayDeque<>(); + todo.add(original); + Set done = new HashSet<>(); + while (!todo.isEmpty()) { + Type type = todo.remove(); + if (done.add(type)) { + Class cls = getRawType(type); + Function, Type> bindings; + if (type instanceof ParameterizedType) { + Type[] typeArguments = ((ParameterizedType) type).getActualTypeArguments(); + TypeVariable>[] typeVariables = cls.getTypeParameters(); + bindings = v -> { + for (int i = 0; i < typeArguments.length; i++) { + Type typeArgument = typeArguments[i]; + if (v.equals(typeVariables[i])) { + return typeArgument; + } + } + return null; + }; + } else { + bindings = v -> null; + } + Type[] interfaces = cls.getGenericInterfaces(); + for (Type itf : interfaces) { + todo.add(bind(itf, bindings)); + } + Type supercls = cls.getGenericSuperclass(); + if (supercls != null) { + todo.add(bind(supercls, bindings)); + } + } + } + return done; + } + + public static Type simplifyType(Type original) { + if (original instanceof Class) { + return original; + } + + if (original instanceof GenericArrayType) { + Type componentType = ((GenericArrayType) original).getGenericComponentType(); + Type repackedComponentType = simplifyType(componentType); + if (componentType != repackedComponentType) { + return genericArrayType(repackedComponentType); + } + return original; + } + + if (original instanceof ParameterizedType) { + ParameterizedType parameterizedType = (ParameterizedType) original; + Type[] typeArguments = parameterizedType.getActualTypeArguments(); + Type[] repackedTypeArguments = simplifyTypes(typeArguments); + + if (isAllObjects(repackedTypeArguments)) { + return parameterizedType.getRawType(); + } + + if (typeArguments != repackedTypeArguments) { + return parameterizedType( + parameterizedType.getOwnerType(), parameterizedType.getRawType(), repackedTypeArguments); + } + return original; + } + + if (original instanceof TypeVariable) { + throw new IllegalArgumentException("Key should not contain a type variable: " + original); + } + + if (original instanceof WildcardType) { + WildcardType wildcardType = (WildcardType) original; + Type[] upperBounds = wildcardType.getUpperBounds(); + if (upperBounds.length == 1) { + Type upperBound = upperBounds[0]; + if (upperBound != Object.class) { + return simplifyType(upperBound); + } + } else if (upperBounds.length > 1) { + throw new IllegalArgumentException("Multiple upper bounds not supported: " + original); + } + + Type[] lowerBounds = wildcardType.getLowerBounds(); + if (lowerBounds.length == 1) { + return simplifyType(lowerBounds[0]); + } else if (lowerBounds.length > 1) { + throw new IllegalArgumentException("Multiple lower bounds not supported: " + original); + } + return Object.class; + } + + return original; + } + + private static Type[] simplifyTypes(Type[] original) { + int length = original.length; + for (int i = 0; i < length; i++) { + Type typeArgument = original[i]; + Type repackTypeArgument = simplifyType(typeArgument); + if (repackTypeArgument != typeArgument) { + Type[] repackedTypeArguments = new Type[length]; + System.arraycopy(original, 0, repackedTypeArguments, 0, i); + repackedTypeArguments[i++] = repackTypeArgument; + for (; i < length; i++) { + repackedTypeArguments[i] = simplifyType(original[i]); + } + return repackedTypeArguments; + } + } + return original; + } + + private static boolean isAllObjects(Type[] types) { + for (Type type : types) { + if (type != Object.class) { + return false; + } + } + return true; + } + + /** + * Tests whether a {@code from} type is assignable to {@code to} type + * + * @param to a 'to' type that should be checked for possible assignment + * @param from a 'from' type that should be checked for possible assignment + * @return whether an object of type {@code from} is assignable to an object of type {@code to} + */ + public static boolean isAssignable(Type to, Type from) { + // shortcut + if (to instanceof Class && from instanceof Class) { + return ((Class) to).isAssignableFrom((Class) from); + } + return isAssignable(to, from, false); + } + + 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; + toUppers = wildcardTo.getUpperBounds(); + toLowers = wildcardTo.getLowerBounds(); + } else { + toUppers = new Type[] {to}; + toLowers = strict ? toUppers : NO_TYPES; + } + + Type[] fromUppers, fromLowers; + if (from instanceof WildcardType) { + WildcardType wildcardTo = (WildcardType) to; + fromUppers = wildcardTo.getUpperBounds(); + fromLowers = wildcardTo.getLowerBounds(); + } else { + fromUppers = new Type[] {from}; + fromLowers = strict ? fromUppers : NO_TYPES; + } + + for (Type toUpper : toUppers) { + for (Type fromUpper : fromUppers) { + if (!isAssignable(toUpper, fromUpper, false)) { + return false; + } + } + } + if (toLowers.length == 0) { + return true; + } + if (fromLowers.length == 0) { + return false; + } + for (Type toLower : toLowers) { + for (Type fromLower : fromLowers) { + if (!isAssignable(fromLower, toLower, false)) { + return false; + } + } + } + return true; + } + if (to instanceof GenericArrayType) { + to = getRawType(to); + } + if (from instanceof GenericArrayType) { + from = getRawType(from); + } + if (!strict && to instanceof Class) { + return ((Class) to).isAssignableFrom(getRawType(from)); + } + Class toRawClazz = getRawType(to); + Type[] toTypeArguments = getActualTypeArguments(to); + return isAssignable(toRawClazz, toTypeArguments, from, strict); + } + + private static boolean isAssignable(Class toRawClazz, Type[] toTypeArguments, Type from, boolean strict) { + Class fromRawClazz = getRawType(from); + if (strict && !toRawClazz.equals(fromRawClazz)) { + return false; + } + if (!strict && !toRawClazz.isAssignableFrom(fromRawClazz)) { + return false; + } + if (toRawClazz.isArray()) { + return true; + } + Type[] fromTypeArguments = getActualTypeArguments(from); + if (toRawClazz == fromRawClazz) { + if (toTypeArguments.length > fromTypeArguments.length) { + return false; + } + for (int i = 0; i < toTypeArguments.length; i++) { + if (!isAssignable(toTypeArguments[i], fromTypeArguments[i], true)) { + return false; + } + } + return true; + } + Map, Type> typeBindings = getTypeBindings(from); + for (Type anInterface : fromRawClazz.getGenericInterfaces()) { + if (isAssignable( + toRawClazz, + toTypeArguments, + bind(anInterface, key -> typeBindings.getOrDefault(key, wildcardTypeAny())), + false)) { + return true; + } + } + Type superclass = fromRawClazz.getGenericSuperclass(); + return superclass != null && isAssignable(toRawClazz, toTypeArguments, bind(superclass, typeBindings), false); + } + public static final class ParameterizedTypeImpl implements ParameterizedType { private final @Nullable Type ownerType; private final Type rawType; diff --git a/maven-di/src/main/java/org/apache/maven/di/processor/IndexAnnotationProcessor.java b/maven-di/src/main/java/org/apache/maven/di/processor/IndexAnnotationProcessor.java deleted file mode 100644 index ad6950671dab..000000000000 --- a/maven-di/src/main/java/org/apache/maven/di/processor/IndexAnnotationProcessor.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * 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.processor; - -import javax.annotation.processing.Completion; -import javax.annotation.processing.ProcessingEnvironment; -import javax.annotation.processing.Processor; -import javax.annotation.processing.RoundEnvironment; -import javax.lang.model.SourceVersion; -import javax.lang.model.element.*; -import javax.tools.Diagnostic; -import javax.tools.StandardLocation; -import javax.xml.stream.XMLStreamReader; - -import java.io.BufferedWriter; -import java.io.IOException; -import java.io.Reader; -import java.io.Writer; -import java.util.*; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.ctc.wstx.stax.WstxInputFactory; -import org.apache.maven.api.di.Qualifier; -import org.apache.maven.api.xml.XmlNode; -import org.apache.maven.internal.xml.XmlNodeBuilder; - -public class IndexAnnotationProcessor implements Processor { - - private ProcessingEnvironment environment; - private Set index = new TreeSet<>(); - - @Override - public void init(ProcessingEnvironment processingEnv) { - this.environment = processingEnv; - } - - @Override - public boolean process(Set annotations, RoundEnvironment roundEnv) { - for (TypeElement annotation : annotations) { - if (annotation.getAnnotation(Qualifier.class) != null) { - for (Element elem : roundEnv.getElementsAnnotatedWith(annotation)) { - if (elem.getKind().isClass()) { - addClassToIndex(environment - .getElementUtils() - .getBinaryName((TypeElement) elem) - .toString()); - } - } - } - } - for (Element elem : roundEnv.getElementsAnnotatedWith(org.apache.maven.api.plugin.annotations.Mojo.class)) { - PackageElement packageElement = environment.getElementUtils().getPackageOf(elem); - String packageName = packageElement.getQualifiedName().toString(); - String generatorClassName = elem.getSimpleName().toString() + "Factory"; - - String mojoName = elem.getAnnotation(org.apache.maven.api.plugin.annotations.Mojo.class) - .name(); - - try { - Reader reader = environment - .getFiler() - .getResource(StandardLocation.CLASS_OUTPUT, "", "META-INF/maven/plugin.xml") - .openReader(true); - XMLStreamReader parser = WstxInputFactory.newFactory().createXMLStreamReader(reader); - XmlNode plugin = XmlNodeBuilder.build(parser, null); - String groupId = plugin.getChild("groupId").getValue(); - String artifactId = plugin.getChild("artifactId").getValue(); - String version = plugin.getChild("version").getValue(); - - Writer file = environment - .getFiler() - .createSourceFile(packageName + "." + generatorClassName) - .openWriter(); - file.write("package " + packageName + ";\n"); - file.write("public class " + generatorClassName + " {\n"); - file.write(" @org.apache.maven.api.di.Named(\"" + groupId + ":" + artifactId + ":" + version + ":" - + mojoName + "\")\n"); - file.write(" @org.apache.maven.api.di.Provides\n"); - file.write(" public static " + ((TypeElement) elem).getQualifiedName() + " create() {\n"); - file.write(" return new " + ((TypeElement) elem).getQualifiedName() + "();\n"); - file.write(" }\n"); - file.write("}\n"); - file.flush(); - file.close(); - } catch (Exception ex) { - Logger.getLogger(IndexAnnotationProcessor.class.getName()).log(Level.SEVERE, null, ex); - } - - addClassToIndex(packageName + "." + generatorClassName); - } - if (roundEnv.processingOver()) { - flushIndex(); - } - return false; - } - - protected void addClassToIndex(String className) { - index.add(className); - } - - protected void flushIndex() { - try (BufferedWriter writer = new BufferedWriter(environment - .getFiler() - .createResource(StandardLocation.CLASS_OUTPUT, "", "META-INF/maven/org.apache.maven.api.di.Inject") - .openWriter())) { - for (String line : index) { - writer.write(line); - writer.newLine(); - } - } catch (IOException e) { - environment.getMessager().printMessage(Diagnostic.Kind.WARNING, e.toString()); - } - } - - @Override - public Iterable getCompletions( - Element element, AnnotationMirror annotation, ExecutableElement member, String userText) { - return Collections.emptySet(); - } - - @Override - public Set getSupportedAnnotationTypes() { - return Collections.singleton("*"); - } - - @Override - public Set getSupportedOptions() { - return Collections.emptySet(); - } - - @Override - public SourceVersion getSupportedSourceVersion() { - return SourceVersion.latestSupported(); - } -} diff --git a/maven-di/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/maven-di/src/main/resources/META-INF/services/javax.annotation.processing.Processor deleted file mode 100644 index e219e2ed643c..000000000000 --- a/maven-di/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ /dev/null @@ -1 +0,0 @@ -org.apache.maven.di.processor.IndexAnnotationProcessor diff --git a/maven-di/src/test/java/org/apache/maven/di/impl/DITest.java b/maven-di/src/test/java/org/apache/maven/di/impl/InjectorImplTest.java similarity index 88% rename from maven-di/src/test/java/org/apache/maven/di/impl/DITest.java rename to maven-di/src/test/java/org/apache/maven/di/impl/InjectorImplTest.java index 3e70ef5310b4..4611154065e2 100644 --- a/maven-di/src/test/java/org/apache/maven/di/impl/DITest.java +++ b/maven-di/src/test/java/org/apache/maven/di/impl/InjectorImplTest.java @@ -20,6 +20,7 @@ import java.lang.annotation.Retention; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -33,7 +34,7 @@ import static org.junit.jupiter.api.Assertions.*; @SuppressWarnings("unused") -public class DITest { +public class InjectorImplTest { @Test void markerQualifierTest() { @@ -274,4 +275,47 @@ static class Bean2 { int num = bean2.incrementAndGet(); } } + + @Test + void testProvides() { + Injector injector = Injector.create().bindImplicit(ProvidesContainer.class); + + assertNotNull(injector.getInstance(String.class)); + } + + static class ProvidesContainer { + + @Provides + static ArrayList newStringList() { + return new ArrayList<>(Arrays.asList("foo", "bar")); + } + + @Provides + static String newStringOfList(List list) { + return list.toString(); + } + } + + @Test + void testInjectConstructor() { + Injector injector = Injector.create().bindImplicit(InjectConstructorContainer.class); + + assertNotNull(injector.getInstance(InjectConstructorContainer.Bean.class)); + } + + static class InjectConstructorContainer { + @Named + static class Bean { + @Inject + Bean(Another another, Third third) {} + + Bean() {} + } + + @Named + static class Another {} + + @Named + static class Third {} + } } diff --git a/maven-di/src/test/java/org/apache/maven/di/impl/TypeT.java b/maven-di/src/test/java/org/apache/maven/di/impl/TypeT.java deleted file mode 100644 index 39db8f7b2e56..000000000000 --- a/maven-di/src/test/java/org/apache/maven/di/impl/TypeT.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * 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 java.lang.reflect.AnnotatedParameterizedType; -import java.lang.reflect.AnnotatedType; -import java.lang.reflect.Type; - -/** - * A type token for defining complex types (annotated, parameterized) - *

- * Usage example: - *

- * {@code Type listOfStringsType = new TypeT>(){}.getType()} - * - * @param actual type - */ -public abstract class TypeT { - private final AnnotatedType annotatedType; - - /** - * Creates a new type token. A type argument {@link T} must be specified. - * A typical usage is: - *

- * {@code TypeT> integerListTypeT = new TypeT>(){};} - * - * @throws AssertionError if a {@link TypeT} is created with a raw type - */ - protected TypeT() { - this.annotatedType = getSuperclassTypeParameter(this.getClass()); - } - - private static AnnotatedType getSuperclassTypeParameter(Class subclass) { - AnnotatedType superclass = subclass.getAnnotatedSuperclass(); - if (superclass instanceof AnnotatedParameterizedType) { - return ((AnnotatedParameterizedType) superclass).getAnnotatedActualTypeArguments()[0]; - } - throw new AssertionError(); - } - - /** - * Returns an {@link AnnotatedType} of a {@link T} - */ - public final AnnotatedType getAnnotatedType() { - return annotatedType; - } - - /** - * Returns a {@link Type} of a {@link T} - */ - public final Type getType() { - return annotatedType.getType(); - } - - /** - * Returns a raw type (e.g {@link Class}) of a {@link T} - */ - @SuppressWarnings("unchecked") - public final Class getRawType() { - return (Class) Types.getRawType(annotatedType.getType()); - } - - @Override - public final String toString() { - return annotatedType.toString(); - } -} diff --git a/maven-di/src/test/java/org/apache/maven/di/impl/TypeUtilsTest.java b/maven-di/src/test/java/org/apache/maven/di/impl/TypeUtilsTest.java index 2bf26477eb9f..d326d41fd721 100644 --- a/maven-di/src/test/java/org/apache/maven/di/impl/TypeUtilsTest.java +++ b/maven-di/src/test/java/org/apache/maven/di/impl/TypeUtilsTest.java @@ -20,14 +20,43 @@ import java.lang.reflect.Type; import java.util.*; +import java.util.stream.Collectors; +import org.apache.maven.di.Key; import org.junit.jupiter.api.Test; -import static org.apache.maven.di.impl.TypeUtils.simplifyType; +import static org.apache.maven.di.impl.Types.simplifyType; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; public class TypeUtilsTest { + TreeSet aField; + + @Test + void testGetSuperTypes() { + Type type = new Key>() {}.getType(); + Set types = Types.getAllSuperTypes(type); + assertNotNull(types); + List typesStr = types.stream().map(Type::toString).sorted().collect(Collectors.toList()); + typesStr.remove("java.util.SequencedSet"); + typesStr.remove("java.util.SequencedCollection"); + assertEquals( + Arrays.asList( + "class java.lang.Object", + "interface java.io.Serializable", + "interface java.lang.Cloneable", + "java.lang.Iterable", + "java.util.AbstractCollection", + "java.util.AbstractSet", + "java.util.Collection", + "java.util.NavigableSet", + "java.util.Set", + "java.util.SortedSet", + "java.util.TreeSet"), + typesStr); + } + @Test public void testSimplifyType() { { @@ -36,96 +65,96 @@ public void testSimplifyType() { } { - Type type = new TypeT>() {}.getType(); + Type type = new Key>() {}.getType(); assertEquals(type, simplifyType(type)); } { - Type type = new TypeT>>>() {}.getType(); + Type type = new Key>>>() {}.getType(); assertEquals(type, simplifyType(type)); } { - Type type = new TypeT>() {}.getType(); - Type expected = new TypeT>() {}.getType(); + Type type = new Key>() {}.getType(); + Type expected = new Key>() {}.getType(); assertEquals(expected, simplifyType(type)); } { - Type type = new TypeT>>>() {}.getType(); - Type expected = new TypeT>>>() {}.getType(); + Type type = new Key>>>() {}.getType(); + Type expected = new Key>>>() {}.getType(); assertEquals(expected, simplifyType(type)); } { - Type type = new TypeT>>>() {}.getType(); - Type expected = new TypeT>>>() {}.getType(); + Type type = new Key>>>() {}.getType(); + Type expected = new Key>>>() {}.getType(); assertEquals(expected, simplifyType(type)); } { - Type type = new TypeT>() {}.getType(); - Type expected = new TypeT>() {}.getType(); + Type type = new Key>() {}.getType(); + Type expected = new Key>() {}.getType(); assertEquals(expected, simplifyType(type)); } { - Type type = new TypeT>>>() {}.getType(); - Type expected = new TypeT>>>() {}.getType(); + Type type = new Key>>>() {}.getType(); + Type expected = new Key>>>() {}.getType(); assertEquals(expected, simplifyType(type)); } { - Type type = new TypeT>>>() {}.getType(); - Type expected = new TypeT>>>() {}.getType(); + Type type = new Key>>>() {}.getType(); + Type expected = new Key>>>() {}.getType(); assertEquals(expected, simplifyType(type)); } { - Type type = new TypeT>>>() {}.getType(); - Type expected = new TypeT>>>() {}.getType(); + Type type = new Key>>>() {}.getType(); + Type expected = new Key>>>() {}.getType(); assertEquals(expected, simplifyType(type)); } { - Type type = new TypeT[]>() {}.getType(); - Type expected = new TypeT[]>() {}.getType(); + Type type = new Key[]>() {}.getType(); + Type expected = new Key[]>() {}.getType(); assertEquals(expected, simplifyType(type)); } { - Type type = new TypeT[]>() {}.getType(); - Type expected = new TypeT[]>() {}.getType(); + Type type = new Key[]>() {}.getType(); + Type expected = new Key[]>() {}.getType(); assertEquals(expected, simplifyType(type)); } { - Type type = new TypeT>() {}.getType(); - Type expected = new TypeT>() {}.getType(); + Type type = new Key>() {}.getType(); + Type expected = new Key>() {}.getType(); assertEquals(expected, simplifyType(type)); } { - Type type = new TypeT>() {}.getType(); - Type expected = new TypeT>() {}.getType(); + Type type = new Key>() {}.getType(); + Type expected = new Key>() {}.getType(); assertEquals(expected, simplifyType(type)); } { - Type type = new TypeT>() {}.getType(); + Type type = new Key>() {}.getType(); Type expected = TestClass.class; assertEquals(expected, simplifyType(type)); } { - Type type = new TypeT>() {}.getType(); + Type type = new Key>() {}.getType(); Type expected = TestClass.class; assertEquals(expected, simplifyType(type)); } { //noinspection TypeParameterExplicitlyExtendsObject - Type type = new TypeT< + Type type = new Key< TestClass< Integer, ? extends Integer, @@ -136,7 +165,7 @@ public void testSimplifyType() { ?, Set>, Set>>>() {}.getType(); - Type expected = new TypeT< + Type expected = new Key< TestClass< Integer, Integer, From a164095d051b9d754938a862e30c282732722363 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Fri, 9 Feb 2024 11:45:47 +0100 Subject: [PATCH 2/3] [API] Add a PluginXmlFactory --- .../api/services/xml/PluginXmlFactory.java | 30 +++++ .../impl/DefaultPluginXmlFactory.java | 122 ++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 api/maven-api-core/src/main/java/org/apache/maven/api/services/xml/PluginXmlFactory.java create mode 100644 maven-core/src/main/java/org/apache/maven/internal/impl/DefaultPluginXmlFactory.java diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/xml/PluginXmlFactory.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/xml/PluginXmlFactory.java new file mode 100644 index 000000000000..36b089723cec --- /dev/null +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/xml/PluginXmlFactory.java @@ -0,0 +1,30 @@ +/* + * 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.api.services.xml; + +import org.apache.maven.api.annotations.Experimental; +import org.apache.maven.api.plugin.descriptor.PluginDescriptor; + +/** + * Reads and writes a {@link PluginDescriptor} object to/from XML. + * + * @since 4.0.0 + */ +@Experimental +public interface PluginXmlFactory extends XmlFactory {} diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultPluginXmlFactory.java b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultPluginXmlFactory.java new file mode 100644 index 000000000000..dc919b9d2ad4 --- /dev/null +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultPluginXmlFactory.java @@ -0,0 +1,122 @@ +/* + * 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; + +import javax.inject.Named; +import javax.inject.Singleton; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.apache.maven.api.annotations.Nonnull; +import org.apache.maven.api.plugin.descriptor.PluginDescriptor; +import org.apache.maven.api.services.xml.*; +import org.apache.maven.plugin.descriptor.io.PluginDescriptorStaxReader; +import org.apache.maven.plugin.descriptor.io.PluginDescriptorStaxWriter; + +import static org.apache.maven.internal.impl.Utils.nonNull; + +@Named +@Singleton +public class DefaultPluginXmlFactory implements PluginXmlFactory { + @Override + public PluginDescriptor read(@Nonnull XmlReaderRequest request) throws XmlReaderException { + nonNull(request, "request"); + Path path = request.getPath(); + URL url = request.getURL(); + Reader reader = request.getReader(); + InputStream inputStream = request.getInputStream(); + if (path == null && url == null && reader == null && inputStream == null) { + throw new IllegalArgumentException("path, url, reader or inputStream must be non null"); + } + try { + PluginDescriptorStaxReader xml = new PluginDescriptorStaxReader(); + xml.setAddDefaultEntities(request.isAddDefaultEntities()); + if (inputStream != null) { + return xml.read(inputStream, request.isStrict()); + } else if (reader != null) { + return xml.read(reader, request.isStrict()); + } else if (path != null) { + try (InputStream is = Files.newInputStream(path)) { + return xml.read(is, request.isStrict()); + } + } else { + try (InputStream is = url.openStream()) { + return xml.read(is, request.isStrict()); + } + } + } catch (Exception e) { + throw new XmlReaderException("Unable to read model", e); + } + } + + @Override + public void write(XmlWriterRequest request) throws XmlWriterException { + nonNull(request, "request"); + PluginDescriptor content = nonNull(request.getContent(), "content"); + Path path = request.getPath(); + OutputStream outputStream = request.getOutputStream(); + Writer writer = request.getWriter(); + if (writer == null && outputStream == null && path == null) { + throw new IllegalArgumentException("writer, outputStream or path must be non null"); + } + try { + if (writer != null) { + new PluginDescriptorStaxWriter().write(writer, content); + } else if (outputStream != null) { + new PluginDescriptorStaxWriter().write(outputStream, content); + } else { + try (OutputStream os = Files.newOutputStream(path)) { + new PluginDescriptorStaxWriter().write(outputStream, content); + } + } + } catch (Exception e) { + throw new XmlWriterException("Unable to write model", e); + } + } + + /** + * Simply parse the given xml string. + * + * @param xml the input xml string + * @return the parsed object + * @throws XmlReaderException if an error occurs during the parsing + * @see #toXmlString(Object) + */ + public static PluginDescriptor fromXml(@Nonnull String xml) throws XmlReaderException { + return new DefaultPluginXmlFactory().fromXmlString(xml); + } + + /** + * Simply converts the given content to an xml string. + * + * @param content the object to convert + * @return the xml string representation + * @throws XmlWriterException if an error occurs during the transformation + * @see #fromXmlString(String) + */ + public static String toXml(@Nonnull PluginDescriptor content) throws XmlWriterException { + return new DefaultPluginXmlFactory().toXmlString(content); + } +} From dcadaee525f99b97b3aacd1e7cecc5122f2d56a0 Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Fri, 9 Feb 2024 13:06:21 +0100 Subject: [PATCH 3/3] [API] Use ProjectScope for accessing compile source roots and resources # Conflicts: # api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectManager.java --- .../maven/api/services/ProjectManager.java | 61 +++++++++----- .../internal/impl/DefaultProjectManager.java | 80 +++++++++++-------- 2 files changed, 87 insertions(+), 54 deletions(-) diff --git a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectManager.java b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectManager.java index 761a519d25db..d4b38472765d 100644 --- a/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectManager.java +++ b/api/maven-api-core/src/main/java/org/apache/maven/api/services/ProjectManager.java @@ -19,16 +19,9 @@ package org.apache.maven.api.services; import java.nio.file.Path; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import org.apache.maven.api.Artifact; -import org.apache.maven.api.Project; -import org.apache.maven.api.RemoteRepository; -import org.apache.maven.api.Service; -import org.apache.maven.api.Session; +import java.util.*; + +import org.apache.maven.api.*; import org.apache.maven.api.annotations.Experimental; import org.apache.maven.api.annotations.Nonnull; import org.apache.maven.api.annotations.Nullable; @@ -86,21 +79,45 @@ default void attachArtifact(Session session, Project project, String type, Path void attachArtifact(Project project, Artifact artifact, Path path); - List getCompileSourceRoots(Project project); - - void addCompileSourceRoot(Project project, String sourceRoot); - - List getTestCompileSourceRoots(Project project); - - void addTestCompileSourceRoot(Project project, String sourceRoot); - - List getResources(Project project); + /** + * Obtain an immutable list of compile source roots for the given project and scope. + * Paths are absolute. + * + * @param project the project + * @param scope the scope, i.e. usually main or test + * @return the list of compile source roots + */ + @Nonnull + List getCompileSourceRoots(@Nonnull Project project, @Nonnull ProjectScope scope); - void addResource(Project project, Resource resource); + /** + * Add a compilation source root to the given project for the given scope. + * The path will be transformed into an absolute path and added to the list for the given scope, + * if not already present. + * + * @param project the project + * @param scope the scope, i.e. usually main or test + * @param sourceRoot the new source root + */ + void addCompileSourceRoot(@Nonnull Project project, @Nonnull ProjectScope scope, @Nonnull Path sourceRoot); - List getTestResources(Project project); + /** + * Get the list of resources for the given project and scope + * + * @param project the project + * @param scope the scope, i.e. usually main or test + * @return the list of resources + */ + List getResources(@Nonnull Project project, @Nonnull ProjectScope scope); - void addTestResource(Project project, Resource resource); + /** + * Add a resource set to the given project for the given scope. + * + * @param project the project + * @param scope the scope, i.e. usually main or test + * @param resource the resource set to add + */ + void addResource(@Nonnull Project project, @Nonnull ProjectScope scope, @Nonnull Resource resource); /** * Returns an immutable list of project remote repositories (directly specified or inherited). diff --git a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java index d71525fba82c..892e7f1e33f2 100644 --- a/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java +++ b/maven-core/src/main/java/org/apache/maven/internal/impl/DefaultProjectManager.java @@ -22,7 +22,9 @@ import javax.inject.Named; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; +import java.util.stream.Collectors; import org.apache.maven.RepositoryUtils; import org.apache.maven.api.*; @@ -33,7 +35,9 @@ import org.apache.maven.project.MavenProject; import org.eclipse.sisu.Typed; +import static java.util.stream.Collectors.toList; import static org.apache.maven.internal.impl.Utils.map; +import static org.apache.maven.internal.impl.Utils.nonNull; @Named @Typed @@ -86,47 +90,59 @@ public void attachArtifact(Project project, Artifact artifact, Path path) { } @Override - public List getCompileSourceRoots(Project project) { - List roots = getMavenProject(project).getCompileSourceRoots(); - return Collections.unmodifiableList(roots); - } - - @Override - public void addCompileSourceRoot(Project project, String sourceRoot) { - List roots = getMavenProject(project).getCompileSourceRoots(); - roots.add(sourceRoot); - } - - @Override - public List getTestCompileSourceRoots(Project project) { - List roots = getMavenProject(project).getTestCompileSourceRoots(); - return Collections.unmodifiableList(roots); - } - - @Override - public void addTestCompileSourceRoot(Project project, String sourceRoot) { - List roots = getMavenProject(project).getTestCompileSourceRoots(); - roots.add(sourceRoot); - } - - @Override - public List getResources(Project project) { - return getMavenProject(project).getBuild().getDelegate().getResources(); + public List getCompileSourceRoots(Project project, ProjectScope scope) { + MavenProject prj = getMavenProject(nonNull(project, "project")); + List roots; + if (nonNull(scope, "scope") == ProjectScope.MAIN) { + roots = prj.getCompileSourceRoots(); + } else if (scope == ProjectScope.TEST) { + roots = prj.getTestCompileSourceRoots(); + } else { + throw new IllegalArgumentException("Unsupported scope " + scope); + } + return roots.stream() + .map(Paths::get) + .collect(Collectors.collectingAndThen(toList(), Collections::unmodifiableList)); } @Override - public void addResource(Project project, Resource resource) { - getMavenProject(project).addResource(new org.apache.maven.model.Resource(resource)); + public void addCompileSourceRoot(Project project, ProjectScope scope, Path sourceRoot) { + MavenProject prj = getMavenProject(nonNull(project, "project")); + String root = nonNull(sourceRoot, "sourceRoot").toAbsolutePath().toString(); + if (nonNull(scope, "scope") == ProjectScope.MAIN) { + prj.addCompileSourceRoot(root); + } else if (scope == ProjectScope.TEST) { + prj.addTestCompileSourceRoot(root); + } else { + throw new IllegalArgumentException("Unsupported scope " + scope); + } } @Override - public List getTestResources(Project project) { - return getMavenProject(project).getBuild().getDelegate().getTestResources(); + public List getResources(@Nonnull Project project, @Nonnull ProjectScope scope) { + Project prj = nonNull(project, "project"); + if (nonNull(scope, "scope") == ProjectScope.MAIN) { + return prj.getBuild().getResources(); + } else if (scope == ProjectScope.TEST) { + return prj.getBuild().getTestResources(); + } else { + throw new IllegalArgumentException("Unsupported scope " + scope); + } } @Override - public void addTestResource(Project project, Resource resource) { - getMavenProject(project).addTestResource(new org.apache.maven.model.Resource(resource)); + public void addResource(@Nonnull Project project, @Nonnull ProjectScope scope, @Nonnull Resource resource) { + // TODO: we should not modify the underlying model here, but resources should be stored + // TODO: in a separate field in the project, however, that could break v3 plugins + MavenProject prj = getMavenProject(nonNull(project, "project")); + org.apache.maven.model.Resource res = new org.apache.maven.model.Resource(nonNull(resource, "resource")); + if (nonNull(scope, "scope") == ProjectScope.MAIN) { + prj.addResource(res); + } else if (scope == ProjectScope.TEST) { + prj.addTestResource(res); + } else { + throw new IllegalArgumentException("Unsupported scope " + scope); + } } @Override