18
18
import static com .tngtech .archunit .core .domain .JavaClass .Predicates .*;
19
19
import static com .tngtech .archunit .core .domain .properties .CanBeAnnotated .Predicates .*;
20
20
import static com .tngtech .archunit .core .domain .properties .HasModifiers .Predicates .*;
21
- import static java .util .stream .Collectors .*;
22
21
import static org .springframework .modulith .core .SyntacticSugar .*;
23
22
24
23
import java .lang .annotation .Annotation ;
25
24
import java .util .Collection ;
26
25
import java .util .Comparator ;
27
26
import java .util .Iterator ;
27
+ import java .util .Map .Entry ;
28
28
import java .util .Objects ;
29
29
import java .util .Optional ;
30
- import java .util .Set ;
30
+ import java .util .SortedSet ;
31
+ import java .util .TreeMap ;
32
+ import java .util .TreeSet ;
31
33
import java .util .function .BiPredicate ;
32
34
import java .util .function .Predicate ;
33
35
import java .util .function .Supplier ;
@@ -58,32 +60,44 @@ public class JavaPackage implements DescribedIterable<JavaClass>, Comparable<Jav
58
60
has (simpleName (PACKAGE_INFO_NAME )).or (is (metaAnnotatedWith (PackageInfo .class )));
59
61
60
62
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 ;
63
65
private final Supplier <JavaPackages > subPackages ;
64
66
65
67
/**
66
68
* Creates a new {@link JavaPackage} for the given {@link Classes}, name and whether to include all sub-packages.
67
69
*
68
70
* @param classes must not be {@literal null}.
69
71
* @param name must not be {@literal null}.
70
- * @param includeSubPackages
72
+ * @param includeSubPackages whether to include sub-packages.
71
73
*/
72
74
private JavaPackage (Classes classes , PackageName name , boolean includeSubPackages ) {
73
75
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
+
74
91
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!" );
75
94
76
- this .classes = classes ;
77
- this .packageClasses = classes
78
- .that (resideInAPackage (name .asFilter (includeSubPackages )));
95
+ this .classes = classes .that (resideInAPackage (name .asFilter (true )));
79
96
this .name = name ;
80
-
81
- this .directSubPackages = ( ) -> detectSubPackages ()
97
+ this . subPackages = subpackages ;
98
+ this .directSubPackages = SingletonSupplier . of (( ) -> subPackages . get (). stream ()
82
99
.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 )));
87
101
}
88
102
89
103
/**
@@ -167,7 +181,7 @@ public Collection<JavaPackage> getDirectSubPackages() {
167
181
* @return will never be {@literal null}.
168
182
*/
169
183
public Classes getClasses () {
170
- return packageClasses ;
184
+ return classes ;
171
185
}
172
186
173
187
/**
@@ -177,7 +191,7 @@ public Classes getClasses() {
177
191
*/
178
192
public Classes getExposedClasses () {
179
193
180
- return packageClasses //
194
+ return classes //
181
195
.that (doNotHave (simpleName (PACKAGE_INFO_NAME ))) //
182
196
.that (have (modifier (JavaModifier .PUBLIC )));
183
197
}
@@ -192,7 +206,7 @@ public Stream<JavaPackage> getSubPackagesAnnotatedWith(Class<? extends Annotatio
192
206
193
207
Assert .notNull (annotation , "Annotation must not be null!" );
194
208
195
- return packageClasses .that (ARE_PACKAGE_INFOS .and (are (metaAnnotatedWith (annotation )))).stream () //
209
+ return classes .that (ARE_PACKAGE_INFOS .and (are (metaAnnotatedWith (annotation )))).stream () //
196
210
.map (JavaClass ::getPackageName ) //
197
211
.filter (Predicate .not (name ::hasName ))
198
212
.distinct () //
@@ -226,7 +240,7 @@ public Classes that(DescribedPredicate<? super JavaClass> predicate) {
226
240
227
241
Assert .notNull (predicate , "Predicate must not be null!" );
228
242
229
- return packageClasses .that (predicate );
243
+ return classes .that (predicate );
230
244
}
231
245
232
246
/**
@@ -238,7 +252,7 @@ public boolean contains(JavaClass type) {
238
252
239
253
Assert .notNull (type , "Type must not be null!" );
240
254
241
- return packageClasses .contains (type );
255
+ return classes .contains (type );
242
256
}
243
257
244
258
/**
@@ -250,7 +264,7 @@ public boolean contains(String typeName) {
250
264
251
265
Assert .hasText (typeName , "Type name must not be null or empty!" );
252
266
253
- return packageClasses .contains (typeName );
267
+ return classes .contains (typeName );
254
268
}
255
269
256
270
/**
@@ -259,7 +273,7 @@ public boolean contains(String typeName) {
259
273
* @return will never be {@literal null}.
260
274
*/
261
275
public Stream <JavaClass > stream () {
262
- return packageClasses .stream ();
276
+ return classes .stream ();
263
277
}
264
278
265
279
/**
@@ -273,7 +287,7 @@ public <A extends Annotation> Optional<A> getAnnotation(Class<A> annotationType)
273
287
274
288
var isPackageInfo = have (simpleName (PACKAGE_INFO_NAME )).or (are (metaAnnotatedWith (PackageInfo .class )));
275
289
276
- return packageClasses .that (isPackageInfo .and (are (metaAnnotatedWith (annotationType )))) //
290
+ return classes .that (isPackageInfo .and (are (metaAnnotatedWith (annotationType )))) //
277
291
.toOptional () //
278
292
.map (it -> it .reflect ())
279
293
.map (it -> AnnotatedElementUtils .getMergedAnnotation (it , annotationType ));
@@ -343,7 +357,7 @@ Classes getClasses(Iterable<JavaPackage> exclusions) {
343
357
.map (JavaPackage ::asFilter )
344
358
.toArray (String []::new );
345
359
346
- return packageClasses .that (resideOutsideOfPackages (excludedPackages ));
360
+ return classes .that (resideOutsideOfPackages (excludedPackages ));
347
361
}
348
362
349
363
/**
@@ -387,7 +401,7 @@ public <A extends Annotation> Optional<A> findAnnotation(Class<A> annotationType
387
401
388
402
var isPackageInfo = have (simpleName (PACKAGE_INFO_NAME )).or (are (metaAnnotatedWith (PackageInfo .class )));
389
403
390
- var annotatedTypes = toSingle ().packageClasses
404
+ var annotatedTypes = toSingle ().classes
391
405
.that (isPackageInfo .and (are (metaAnnotatedWith (annotationType ))))
392
406
.stream ()
393
407
.map (JavaClass ::reflect )
@@ -427,17 +441,6 @@ public String getDescription() {
427
441
return classes .getDescription ();
428
442
}
429
443
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
-
441
444
/*
442
445
* (non-Javadoc)
443
446
* @see java.lang.Iterable#iterator()
@@ -487,8 +490,7 @@ public boolean equals(Object obj) {
487
490
488
491
return Objects .equals (this .classes , that .classes ) //
489
492
&& 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 );
492
494
}
493
495
494
496
/*
@@ -497,10 +499,40 @@ public boolean equals(Object obj) {
497
499
*/
498
500
@ Override
499
501
public int hashCode () {
500
- return Objects .hash (classes , directSubPackages .get (), name , packageClasses );
502
+ return Objects .hash (classes , directSubPackages .get (), name );
501
503
}
502
504
503
505
static Comparator <JavaPackage > reverse () {
504
506
return (left , right ) -> -left .compareTo (right );
505
507
}
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
+ }
506
538
}
0 commit comments