1818import static com .tngtech .archunit .core .domain .JavaClass .Predicates .*;
1919import static com .tngtech .archunit .core .domain .properties .CanBeAnnotated .Predicates .*;
2020import static com .tngtech .archunit .core .domain .properties .HasModifiers .Predicates .*;
21- import static java .util .stream .Collectors .*;
2221import static org .springframework .modulith .core .SyntacticSugar .*;
2322
2423import java .lang .annotation .Annotation ;
2524import java .util .Collection ;
2625import java .util .Comparator ;
2726import java .util .Iterator ;
27+ import java .util .Map .Entry ;
2828import java .util .Objects ;
2929import java .util .Optional ;
30- import java .util .Set ;
30+ import java .util .SortedSet ;
31+ import java .util .TreeMap ;
32+ import java .util .TreeSet ;
3133import java .util .function .BiPredicate ;
3234import java .util .function .Predicate ;
3335import java .util .function .Supplier ;
@@ -58,32 +60,44 @@ public class JavaPackage implements DescribedIterable<JavaClass>, Comparable<Jav
5860 has (simpleName (PACKAGE_INFO_NAME )).or (is (metaAnnotatedWith (PackageInfo .class )));
5961
6062 private final PackageName name ;
61- private final Classes classes , packageClasses ;
62- private final Supplier <Set <JavaPackage >> directSubPackages ;
63+ private final Classes classes ;
64+ private final Supplier <SortedSet <JavaPackage >> directSubPackages ;
6365 private final Supplier <JavaPackages > subPackages ;
6466
6567 /**
6668 * Creates a new {@link JavaPackage} for the given {@link Classes}, name and whether to include all sub-packages.
6769 *
6870 * @param classes must not be {@literal null}.
6971 * @param name must not be {@literal null}.
70- * @param includeSubPackages
72+ * @param includeSubPackages whether to include sub-packages.
7173 */
7274 private JavaPackage (Classes classes , PackageName name , boolean includeSubPackages ) {
7375
76+ this (classes .that (resideInAPackage (name .asFilter (includeSubPackages ))), name , includeSubPackages
77+ ? SingletonSupplier .of (() -> detectSubPackages (classes , name ))
78+ : SingletonSupplier .of (JavaPackages .NONE ));
79+ }
80+
81+ /**
82+ * Creates a new {@link JavaPackage} for the given {@link Classes}, name and pre-computed sub-packages.
83+ *
84+ * @param classes must not be {@literal null}.
85+ * @param name must not be {@literal null}.
86+ * @param subpackages must not be {@literal null}.
87+ * @see #detectSubPackages(Classes, PackageName)
88+ */
89+ private JavaPackage (Classes classes , PackageName name , Supplier <JavaPackages > subpackages ) {
90+
7491 Assert .notNull (classes , "Classes must not be null!" );
92+ Assert .notNull (name , "PackageName must not be null!" );
93+ Assert .notNull (subpackages , "Sub-packages must not be null!" );
7594
76- this .classes = classes ;
77- this .packageClasses = classes
78- .that (resideInAPackage (name .asFilter (includeSubPackages )));
95+ this .classes = classes .that (resideInAPackage (name .asFilter (true )));
7996 this .name = name ;
80-
81- this .directSubPackages = ( ) -> detectSubPackages ()
97+ this . subPackages = subpackages ;
98+ this .directSubPackages = SingletonSupplier . of (( ) -> subPackages . get (). stream ()
8299 .filter (this ::isDirectParentOf )
83- .collect (Collectors .toUnmodifiableSet ());
84-
85- this .subPackages = SingletonSupplier .of (() -> detectSubPackages ()
86- .collect (collectingAndThen (toUnmodifiableList (), JavaPackages ::new )));
100+ .collect (Collectors .toCollection (TreeSet ::new )));
87101 }
88102
89103 /**
@@ -167,7 +181,7 @@ public Collection<JavaPackage> getDirectSubPackages() {
167181 * @return will never be {@literal null}.
168182 */
169183 public Classes getClasses () {
170- return packageClasses ;
184+ return classes ;
171185 }
172186
173187 /**
@@ -177,7 +191,7 @@ public Classes getClasses() {
177191 */
178192 public Classes getExposedClasses () {
179193
180- return packageClasses //
194+ return classes //
181195 .that (doNotHave (simpleName (PACKAGE_INFO_NAME ))) //
182196 .that (have (modifier (JavaModifier .PUBLIC )));
183197 }
@@ -192,7 +206,7 @@ public Stream<JavaPackage> getSubPackagesAnnotatedWith(Class<? extends Annotatio
192206
193207 Assert .notNull (annotation , "Annotation must not be null!" );
194208
195- return packageClasses .that (ARE_PACKAGE_INFOS .and (are (metaAnnotatedWith (annotation )))).stream () //
209+ return classes .that (ARE_PACKAGE_INFOS .and (are (metaAnnotatedWith (annotation )))).stream () //
196210 .map (JavaClass ::getPackageName ) //
197211 .filter (Predicate .not (name ::hasName ))
198212 .distinct () //
@@ -226,7 +240,7 @@ public Classes that(DescribedPredicate<? super JavaClass> predicate) {
226240
227241 Assert .notNull (predicate , "Predicate must not be null!" );
228242
229- return packageClasses .that (predicate );
243+ return classes .that (predicate );
230244 }
231245
232246 /**
@@ -238,7 +252,7 @@ public boolean contains(JavaClass type) {
238252
239253 Assert .notNull (type , "Type must not be null!" );
240254
241- return packageClasses .contains (type );
255+ return classes .contains (type );
242256 }
243257
244258 /**
@@ -250,7 +264,7 @@ public boolean contains(String typeName) {
250264
251265 Assert .hasText (typeName , "Type name must not be null or empty!" );
252266
253- return packageClasses .contains (typeName );
267+ return classes .contains (typeName );
254268 }
255269
256270 /**
@@ -259,7 +273,7 @@ public boolean contains(String typeName) {
259273 * @return will never be {@literal null}.
260274 */
261275 public Stream <JavaClass > stream () {
262- return packageClasses .stream ();
276+ return classes .stream ();
263277 }
264278
265279 /**
@@ -273,7 +287,7 @@ public <A extends Annotation> Optional<A> getAnnotation(Class<A> annotationType)
273287
274288 var isPackageInfo = have (simpleName (PACKAGE_INFO_NAME )).or (are (metaAnnotatedWith (PackageInfo .class )));
275289
276- return packageClasses .that (isPackageInfo .and (are (metaAnnotatedWith (annotationType )))) //
290+ return classes .that (isPackageInfo .and (are (metaAnnotatedWith (annotationType )))) //
277291 .toOptional () //
278292 .map (it -> it .reflect ())
279293 .map (it -> AnnotatedElementUtils .getMergedAnnotation (it , annotationType ));
@@ -343,7 +357,7 @@ Classes getClasses(Iterable<JavaPackage> exclusions) {
343357 .map (JavaPackage ::asFilter )
344358 .toArray (String []::new );
345359
346- return packageClasses .that (resideOutsideOfPackages (excludedPackages ));
360+ return classes .that (resideOutsideOfPackages (excludedPackages ));
347361 }
348362
349363 /**
@@ -387,7 +401,7 @@ public <A extends Annotation> Optional<A> findAnnotation(Class<A> annotationType
387401
388402 var isPackageInfo = have (simpleName (PACKAGE_INFO_NAME )).or (are (metaAnnotatedWith (PackageInfo .class )));
389403
390- var annotatedTypes = toSingle ().packageClasses
404+ var annotatedTypes = toSingle ().classes
391405 .that (isPackageInfo .and (are (metaAnnotatedWith (annotationType ))))
392406 .stream ()
393407 .map (JavaClass ::reflect )
@@ -427,17 +441,6 @@ public String getDescription() {
427441 return classes .getDescription ();
428442 }
429443
430- private Stream <JavaPackage > detectSubPackages () {
431-
432- return packageClasses .stream () //
433- .map (JavaClass ::getPackageName )
434- .filter (Predicate .not (name ::hasName ))
435- .map (PackageName ::of )
436- .flatMap (name ::expandUntil )
437- .distinct ()
438- .map (it -> new JavaPackage (classes , it , true ));
439- }
440-
441444 /*
442445 * (non-Javadoc)
443446 * @see java.lang.Iterable#iterator()
@@ -487,8 +490,7 @@ public boolean equals(Object obj) {
487490
488491 return Objects .equals (this .classes , that .classes ) //
489492 && Objects .equals (this .getDirectSubPackages (), that .getDirectSubPackages ()) //
490- && Objects .equals (this .name , that .name ) //
491- && Objects .equals (this .packageClasses , that .packageClasses );
493+ && Objects .equals (this .name , that .name );
492494 }
493495
494496 /*
@@ -497,10 +499,40 @@ public boolean equals(Object obj) {
497499 */
498500 @ Override
499501 public int hashCode () {
500- return Objects .hash (classes , directSubPackages .get (), name , packageClasses );
502+ return Objects .hash (classes , directSubPackages .get (), name );
501503 }
502504
503505 static Comparator <JavaPackage > reverse () {
504506 return (left , right ) -> -left .compareTo (right );
505507 }
508+
509+ private static JavaPackages detectSubPackages (Classes classes , PackageName name ) {
510+
511+ var packages = new TreeSet <PackageName >(Comparator .reverseOrder ());
512+
513+ for (JavaClass clazz : classes ) {
514+
515+ var candidate = PackageName .of (clazz .getPackageName ());
516+
517+ if (candidate .equals (name )) {
518+ continue ;
519+ }
520+
521+ name .expandUntil (candidate ).forEach (packages ::add );
522+ }
523+
524+ var result = new TreeMap <PackageName , JavaPackage >();
525+
526+ for (PackageName packageName : packages ) {
527+
528+ Supplier <JavaPackages > subPackages = () -> result .entrySet ().stream ()
529+ .filter (it -> it .getKey ().isSubPackageOf (packageName ))
530+ .map (Entry ::getValue )
531+ .collect (Collectors .collectingAndThen (Collectors .toList (), JavaPackages ::new ));
532+
533+ result .put (packageName , new JavaPackage (classes , packageName , SingletonSupplier .of (subPackages )));
534+ }
535+
536+ return new JavaPackages (result .values ());
537+ }
506538}
0 commit comments