diff --git a/spring-modulith-core/src/main/java/org/springframework/modulith/core/Classes.java b/spring-modulith-core/src/main/java/org/springframework/modulith/core/Classes.java index 90bf2d3e..16eb52c0 100644 --- a/spring-modulith-core/src/main/java/org/springframework/modulith/core/Classes.java +++ b/spring-modulith-core/src/main/java/org/springframework/modulith/core/Classes.java @@ -38,7 +38,6 @@ import com.tngtech.archunit.base.DescribedPredicate; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClasses; import com.tngtech.archunit.core.domain.JavaModifier; -import com.tngtech.archunit.core.domain.JavaType; import com.tngtech.archunit.core.domain.properties.HasName; /** @@ -87,7 +86,8 @@ class Classes implements DescribedIterable { } /** - * Returns a {@link Collector} creating a {@link Classes} instance from a {@link Stream} of {@link JavaType}. + * Returns a {@link Collector} creating a {@link Classes} instance from a {@link Stream} of + * {@link com.tngtech.archunit.core.domain.JavaType}. * * @return will never be {@literal null}. */ @@ -110,6 +110,26 @@ class Classes implements DescribedIterable { .collect(Collectors.collectingAndThen(Collectors.toList(), Classes::new)); } + /** + * Returns all classes that reside the given {@link PackageName}. + * + * @param name must not be {@literal null}. + * @param nested whether to include nested packages + * @return will never be {@literal null}. + */ + Classes thatResideIn(PackageName name, boolean nested) { + + var result = new ArrayList(); + + for (JavaClass candidate : classes) { + if (residesIn(name, candidate, nested)) { + result.add(candidate); + } + } + + return new Classes(result); + } + Classes and(Classes classes) { return and(classes.classes); } @@ -268,6 +288,13 @@ class Classes implements DescribedIterable { return format(type, ""); } + private static boolean residesIn(PackageName reference, JavaClass type, boolean inNested) { + + var typesPackage = PackageName.ofType(type.getFullName()); + + return inNested ? reference.contains(typesPackage) : reference.equals(typesPackage); + } + private static class SameClass extends DescribedPredicate { private final JavaClass reference; 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 977801f1..5f19c989 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 @@ -31,7 +31,6 @@ import java.util.SortedSet; import java.util.TreeMap; import java.util.TreeSet; import java.util.function.BiPredicate; -import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -73,7 +72,7 @@ public class JavaPackage implements DescribedIterable, Comparable detectSubPackages(classes, name)) : SingletonSupplier.of(JavaPackages.NONE)); } @@ -92,7 +91,7 @@ public class JavaPackage implements DescribedIterable, Comparable subPackages.get().stream() @@ -206,15 +205,12 @@ public class JavaPackage implements DescribedIterable, Comparable of(classes, it)); + return getSubPackages().stream() + .filter(it -> it.hasAnnotation(annotation)); } /** - * Returns all sub-packages that match the given {@link BiPredicate} for the canidate package and its trailing name + * Returns all sub-packages that match the given {@link BiPredicate} for the candidate package and its trailing name * relative to the current one. * * @param filter must not be {@literal null}. @@ -388,7 +384,7 @@ public class JavaPackage implements DescribedIterable, Comparable the type of the annotation. * @param annotationType must not be {@literal null}. @@ -502,6 +498,17 @@ public class JavaPackage implements DescribedIterable, Comparable + * @param annotationType + * @return + */ + private boolean hasAnnotation(Class annotationType) { + return findAnnotation(annotationType).isPresent(); + } + static Comparator reverse() { return (left, right) -> -left.compareTo(right); } diff --git a/spring-modulith-core/src/test/java/org/springframework/modulith/core/ClassesUnitTests.java b/spring-modulith-core/src/test/java/org/springframework/modulith/core/ClassesUnitTests.java new file mode 100644 index 00000000..af9cc0ab --- /dev/null +++ b/spring-modulith-core/src/test/java/org/springframework/modulith/core/ClassesUnitTests.java @@ -0,0 +1,51 @@ +/* + * Copyright 2025 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.modulith.core; + +import static org.assertj.core.api.Assertions.*; + +import example.Example; + +import org.junit.jupiter.api.Test; + +import com.tngtech.archunit.core.domain.JavaClass; + +/** + * Unit tests for {@link Classes}. + * + * @author Oliver Drotbohm + */ +class ClassesUnitTests { + + @Test // GH-1098 + void filtersClassesByPackageName() { + + var classes = TestUtils.getClasses(Example.class); + var nestedDirectly = classes.thatResideIn(PackageName.of("example.ni.nested"), false); + + assertThat(nestedDirectly) + .extracting(JavaClass::getSimpleName) + .contains("InNested") + .doesNotContain("InNestedA"); + + var nestedRecursive = classes.thatResideIn(PackageName.of("example.ni.nested"), true); + + assertThat(nestedRecursive) + .extracting(JavaClass::getSimpleName) + .contains("InNested", "InNestedA") + .doesNotContain("ApiType"); + } +}