diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/JavaPackage.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/JavaPackage.java index 412ddd80..15bd0d69 100644 --- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/JavaPackage.java +++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/JavaPackage.java @@ -18,16 +18,18 @@ package org.springframework.modulith.core; import static com.tngtech.archunit.core.domain.JavaClass.Predicates.*; import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.*; import static com.tngtech.archunit.core.domain.properties.HasModifiers.Predicates.*; -import static java.util.stream.Collectors.*; import static org.springframework.modulith.core.SyntacticSugar.*; import java.lang.annotation.Annotation; import java.util.Collection; import java.util.Comparator; import java.util.Iterator; +import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; -import java.util.Set; +import java.util.SortedSet; +import java.util.TreeMap; +import java.util.TreeSet; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -57,8 +59,8 @@ public class JavaPackage implements DescribedIterable, Comparable> directSubPackages; + private final Classes classes; + private final Supplier> directSubPackages; private final Supplier subPackages; /** @@ -66,23 +68,35 @@ public class JavaPackage implements DescribedIterable, Comparable detectSubPackages(classes, name)) + : SingletonSupplier.of(JavaPackages.NONE)); + } + + /** + * Creates a new {@link JavaPackage} for the given {@link Classes}, name and pre-computed sub-packages. + * + * @param classes must not be {@literal null}. + * @param name must not be {@literal null}. + * @param subpackages must not be {@literal null}. + * @see #detectSubPackages(Classes, PackageName) + */ + private JavaPackage(Classes classes, PackageName name, Supplier subpackages) { + Assert.notNull(classes, "Classes must not be null!"); + Assert.notNull(name, "PackageName must not be null!"); + Assert.notNull(subpackages, "Sub-packages must not be null!"); - this.classes = classes; - this.packageClasses = classes - .that(resideInAPackage(name.asFilter(includeSubPackages))); + this.classes = classes.that(resideInAPackage(name.asFilter(true))); this.name = name; - - this.directSubPackages = () -> detectSubPackages() + this.subPackages = subpackages; + this.directSubPackages = SingletonSupplier.of(() -> subPackages.get().stream() .filter(this::isDirectParentOf) - .collect(Collectors.toUnmodifiableSet()); - - this.subPackages = SingletonSupplier.of(() -> detectSubPackages() - .collect(collectingAndThen(toUnmodifiableList(), JavaPackages::new))); + .collect(Collectors.toCollection(TreeSet::new))); } /** @@ -166,7 +180,7 @@ public class JavaPackage implements DescribedIterable, Comparable, Comparable, Comparable, Comparable, Comparable, Comparable, Comparable stream() { - return packageClasses.stream(); + return classes.stream(); } /** @@ -255,7 +269,7 @@ public class JavaPackage implements DescribedIterable, Comparable it.reflect()) .map(it -> AnnotatedElementUtils.getMergedAnnotation(it, annotationType)); @@ -325,7 +339,7 @@ public class JavaPackage implements DescribedIterable, Comparable, Comparable, Comparable detectSubPackages() { - - return packageClasses.stream() // - .map(JavaClass::getPackageName) - .filter(Predicate.not(name::hasName)) - .map(PackageName::of) - .flatMap(name::expandUntil) - .distinct() - .map(it -> new JavaPackage(classes, it, true)); - } - /* * (non-Javadoc) * @see java.lang.Iterable#iterator() @@ -469,8 +472,7 @@ public class JavaPackage implements DescribedIterable, Comparable, Comparable reverse() { return (left, right) -> -left.compareTo(right); } + + private static JavaPackages detectSubPackages(Classes classes, PackageName name) { + + var packages = new TreeSet(Comparator.reverseOrder()); + + for (JavaClass clazz : classes) { + + var candidate = PackageName.of(clazz.getPackageName()); + + if (candidate.equals(name)) { + continue; + } + + name.expandUntil(candidate).forEach(packages::add); + } + + var result = new TreeMap(); + + for (PackageName packageName : packages) { + + Supplier subPackages = () -> result.entrySet().stream() + .filter(it -> it.getKey().isSubPackageOf(packageName)) + .map(Entry::getValue) + .collect(Collectors.collectingAndThen(Collectors.toList(), JavaPackages::new)); + + result.put(packageName, new JavaPackage(classes, packageName, SingletonSupplier.of(subPackages))); + } + + return new JavaPackages(result.values()); + } }