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 .Predicate ;
32
34
import java .util .function .Supplier ;
33
35
import java .util .stream .Collectors ;
@@ -57,32 +59,44 @@ public class JavaPackage implements DescribedIterable<JavaClass>, Comparable<Jav
57
59
has (simpleName (PACKAGE_INFO_NAME )).or (is (metaAnnotatedWith (PackageInfo .class )));
58
60
59
61
private final PackageName name ;
60
- private final Classes classes , packageClasses ;
61
- private final Supplier <Set <JavaPackage >> directSubPackages ;
62
+ private final Classes classes ;
63
+ private final Supplier <SortedSet <JavaPackage >> directSubPackages ;
62
64
private final Supplier <JavaPackages > subPackages ;
63
65
64
66
/**
65
67
* Creates a new {@link JavaPackage} for the given {@link Classes}, name and whether to include all sub-packages.
66
68
*
67
69
* @param classes must not be {@literal null}.
68
70
* @param name must not be {@literal null}.
69
- * @param includeSubPackages
71
+ * @param includeSubPackages whether to include sub-packages.
70
72
*/
71
73
private JavaPackage (Classes classes , PackageName name , boolean includeSubPackages ) {
72
74
75
+ this (classes .that (resideInAPackage (name .asFilter (includeSubPackages ))), name , includeSubPackages
76
+ ? SingletonSupplier .of (() -> detectSubPackages (classes , name ))
77
+ : SingletonSupplier .of (JavaPackages .NONE ));
78
+ }
79
+
80
+ /**
81
+ * Creates a new {@link JavaPackage} for the given {@link Classes}, name and pre-computed sub-packages.
82
+ *
83
+ * @param classes must not be {@literal null}.
84
+ * @param name must not be {@literal null}.
85
+ * @param subpackages must not be {@literal null}.
86
+ * @see #detectSubPackages(Classes, PackageName)
87
+ */
88
+ private JavaPackage (Classes classes , PackageName name , Supplier <JavaPackages > subpackages ) {
89
+
73
90
Assert .notNull (classes , "Classes must not be null!" );
91
+ Assert .notNull (name , "PackageName must not be null!" );
92
+ Assert .notNull (subpackages , "Sub-packages must not be null!" );
74
93
75
- this .classes = classes ;
76
- this .packageClasses = classes
77
- .that (resideInAPackage (name .asFilter (includeSubPackages )));
94
+ this .classes = classes .that (resideInAPackage (name .asFilter (true )));
78
95
this .name = name ;
79
-
80
- this .directSubPackages = ( ) -> detectSubPackages ()
96
+ this . subPackages = subpackages ;
97
+ this .directSubPackages = SingletonSupplier . of (( ) -> subPackages . get (). stream ()
81
98
.filter (this ::isDirectParentOf )
82
- .collect (Collectors .toUnmodifiableSet ());
83
-
84
- this .subPackages = SingletonSupplier .of (() -> detectSubPackages ()
85
- .collect (collectingAndThen (toUnmodifiableList (), JavaPackages ::new )));
99
+ .collect (Collectors .toCollection (TreeSet ::new )));
86
100
}
87
101
88
102
/**
@@ -166,7 +180,7 @@ public Collection<JavaPackage> getDirectSubPackages() {
166
180
* @return will never be {@literal null}.
167
181
*/
168
182
public Classes getClasses () {
169
- return packageClasses ;
183
+ return classes ;
170
184
}
171
185
172
186
/**
@@ -176,7 +190,7 @@ public Classes getClasses() {
176
190
*/
177
191
public Classes getExposedClasses () {
178
192
179
- return packageClasses //
193
+ return classes //
180
194
.that (doNotHave (simpleName (PACKAGE_INFO_NAME ))) //
181
195
.that (have (modifier (JavaModifier .PUBLIC )));
182
196
}
@@ -191,7 +205,7 @@ public Stream<JavaPackage> getSubPackagesAnnotatedWith(Class<? extends Annotatio
191
205
192
206
Assert .notNull (annotation , "Annotation must not be null!" );
193
207
194
- return packageClasses .that (ARE_PACKAGE_INFOS .and (are (metaAnnotatedWith (annotation )))).stream () //
208
+ return classes .that (ARE_PACKAGE_INFOS .and (are (metaAnnotatedWith (annotation )))).stream () //
195
209
.map (JavaClass ::getPackageName ) //
196
210
.filter (Predicate .not (name ::hasName ))
197
211
.distinct () //
@@ -208,7 +222,7 @@ public Classes that(DescribedPredicate<? super JavaClass> predicate) {
208
222
209
223
Assert .notNull (predicate , "Predicate must not be null!" );
210
224
211
- return packageClasses .that (predicate );
225
+ return classes .that (predicate );
212
226
}
213
227
214
228
/**
@@ -220,7 +234,7 @@ public boolean contains(JavaClass type) {
220
234
221
235
Assert .notNull (type , "Type must not be null!" );
222
236
223
- return packageClasses .contains (type );
237
+ return classes .contains (type );
224
238
}
225
239
226
240
/**
@@ -232,7 +246,7 @@ public boolean contains(String typeName) {
232
246
233
247
Assert .hasText (typeName , "Type name must not be null or empty!" );
234
248
235
- return packageClasses .contains (typeName );
249
+ return classes .contains (typeName );
236
250
}
237
251
238
252
/**
@@ -241,7 +255,7 @@ public boolean contains(String typeName) {
241
255
* @return will never be {@literal null}.
242
256
*/
243
257
public Stream <JavaClass > stream () {
244
- return packageClasses .stream ();
258
+ return classes .stream ();
245
259
}
246
260
247
261
/**
@@ -255,7 +269,7 @@ public <A extends Annotation> Optional<A> getAnnotation(Class<A> annotationType)
255
269
256
270
var isPackageInfo = have (simpleName (PACKAGE_INFO_NAME )).or (are (metaAnnotatedWith (PackageInfo .class )));
257
271
258
- return packageClasses .that (isPackageInfo .and (are (metaAnnotatedWith (annotationType )))) //
272
+ return classes .that (isPackageInfo .and (are (metaAnnotatedWith (annotationType )))) //
259
273
.toOptional () //
260
274
.map (it -> it .reflect ())
261
275
.map (it -> AnnotatedElementUtils .getMergedAnnotation (it , annotationType ));
@@ -325,7 +339,7 @@ Classes getClasses(Iterable<JavaPackage> exclusions) {
325
339
.map (JavaPackage ::asFilter )
326
340
.toArray (String []::new );
327
341
328
- return packageClasses .that (resideOutsideOfPackages (excludedPackages ));
342
+ return classes .that (resideOutsideOfPackages (excludedPackages ));
329
343
}
330
344
331
345
/**
@@ -369,7 +383,7 @@ public <A extends Annotation> Optional<A> findAnnotation(Class<A> annotationType
369
383
370
384
var isPackageInfo = have (simpleName (PACKAGE_INFO_NAME )).or (are (metaAnnotatedWith (PackageInfo .class )));
371
385
372
- var annotatedTypes = toSingle ().packageClasses
386
+ var annotatedTypes = toSingle ().classes
373
387
.that (isPackageInfo .and (are (metaAnnotatedWith (annotationType ))))
374
388
.stream ()
375
389
.map (JavaClass ::reflect )
@@ -409,17 +423,6 @@ public String getDescription() {
409
423
return classes .getDescription ();
410
424
}
411
425
412
- private Stream <JavaPackage > detectSubPackages () {
413
-
414
- return packageClasses .stream () //
415
- .map (JavaClass ::getPackageName )
416
- .filter (Predicate .not (name ::hasName ))
417
- .map (PackageName ::of )
418
- .flatMap (name ::expandUntil )
419
- .distinct ()
420
- .map (it -> new JavaPackage (classes , it , true ));
421
- }
422
-
423
426
/*
424
427
* (non-Javadoc)
425
428
* @see java.lang.Iterable#iterator()
@@ -469,8 +472,7 @@ public boolean equals(Object obj) {
469
472
470
473
return Objects .equals (this .classes , that .classes ) //
471
474
&& Objects .equals (this .getDirectSubPackages (), that .getDirectSubPackages ()) //
472
- && Objects .equals (this .name , that .name ) //
473
- && Objects .equals (this .packageClasses , that .packageClasses );
475
+ && Objects .equals (this .name , that .name );
474
476
}
475
477
476
478
/*
@@ -479,10 +481,40 @@ public boolean equals(Object obj) {
479
481
*/
480
482
@ Override
481
483
public int hashCode () {
482
- return Objects .hash (classes , directSubPackages .get (), name , packageClasses );
484
+ return Objects .hash (classes , directSubPackages .get (), name );
483
485
}
484
486
485
487
static Comparator <JavaPackage > reverse () {
486
488
return (left , right ) -> -left .compareTo (right );
487
489
}
490
+
491
+ private static JavaPackages detectSubPackages (Classes classes , PackageName name ) {
492
+
493
+ var packages = new TreeSet <PackageName >(Comparator .reverseOrder ());
494
+
495
+ for (JavaClass clazz : classes ) {
496
+
497
+ var candidate = PackageName .of (clazz .getPackageName ());
498
+
499
+ if (candidate .equals (name )) {
500
+ continue ;
501
+ }
502
+
503
+ name .expandUntil (candidate ).forEach (packages ::add );
504
+ }
505
+
506
+ var result = new TreeMap <PackageName , JavaPackage >();
507
+
508
+ for (PackageName packageName : packages ) {
509
+
510
+ Supplier <JavaPackages > subPackages = () -> result .entrySet ().stream ()
511
+ .filter (it -> it .getKey ().isSubPackageOf (packageName ))
512
+ .map (Entry ::getValue )
513
+ .collect (Collectors .collectingAndThen (Collectors .toList (), JavaPackages ::new ));
514
+
515
+ result .put (packageName , new JavaPackage (classes , packageName , SingletonSupplier .of (subPackages )));
516
+ }
517
+
518
+ return new JavaPackages (result .values ());
519
+ }
488
520
}
0 commit comments