GH-1098 - Optimize type selection in Classes.
We now resort to a simple iteration over the types within a Classes arrangement to detect all classes residing in certain packages. This is primarily used during the JavaPackage data structure construction as it's called for every sub-package of a package originally created from a Classes instance. The new simplified algorithm avoids set up of DescribedPredicate instances to eventually only perform simple package name checks.
This commit is contained in:
@@ -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<JavaClass> {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<JavaClass> {
|
||||
.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<JavaClass>();
|
||||
|
||||
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<JavaClass> {
|
||||
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<JavaClass> {
|
||||
|
||||
private final JavaClass reference;
|
||||
|
||||
@@ -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<JavaClass>, Comparable<Jav
|
||||
*/
|
||||
private JavaPackage(Classes classes, PackageName name, boolean includeSubPackages) {
|
||||
|
||||
this(classes.that(resideInAPackage(name.asFilter(includeSubPackages))), name, includeSubPackages
|
||||
this(classes.thatResideIn(name, includeSubPackages), name, includeSubPackages
|
||||
? SingletonSupplier.of(() -> detectSubPackages(classes, name))
|
||||
: SingletonSupplier.of(JavaPackages.NONE));
|
||||
}
|
||||
@@ -92,7 +91,7 @@ public class JavaPackage implements DescribedIterable<JavaClass>, Comparable<Jav
|
||||
Assert.notNull(name, "PackageName must not be null!");
|
||||
Assert.notNull(subpackages, "Sub-packages must not be null!");
|
||||
|
||||
this.classes = classes.that(resideInAPackage(name.asFilter(true)));
|
||||
this.classes = classes.thatResideIn(name, true);
|
||||
this.name = name;
|
||||
this.subPackages = subpackages;
|
||||
this.directSubPackages = SingletonSupplier.of(() -> subPackages.get().stream()
|
||||
@@ -206,15 +205,12 @@ public class JavaPackage implements DescribedIterable<JavaClass>, Comparable<Jav
|
||||
|
||||
Assert.notNull(annotation, "Annotation must not be null!");
|
||||
|
||||
return classes.that(ARE_PACKAGE_INFOS.and(are(metaAnnotatedWith(annotation)))).stream() //
|
||||
.map(JavaClass::getPackageName) //
|
||||
.filter(Predicate.not(name::hasName))
|
||||
.distinct() //
|
||||
.map(it -> 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<JavaClass>, Comparable<Jav
|
||||
|
||||
/**
|
||||
* Finds the annotation of the given type declared on the package itself or any type located the direct package's
|
||||
* types .
|
||||
* types.
|
||||
*
|
||||
* @param <A> the type of the annotation.
|
||||
* @param annotationType must not be {@literal null}.
|
||||
@@ -502,6 +498,17 @@ public class JavaPackage implements DescribedIterable<JavaClass>, Comparable<Jav
|
||||
return Objects.hash(classes, directSubPackages.get(), name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the current {@link JavaPackage}
|
||||
*
|
||||
* @param <A>
|
||||
* @param annotationType
|
||||
* @return
|
||||
*/
|
||||
private <A extends Annotation> boolean hasAnnotation(Class<A> annotationType) {
|
||||
return findAnnotation(annotationType).isPresent();
|
||||
}
|
||||
|
||||
static Comparator<JavaPackage> reverse() {
|
||||
return (left, right) -> -left.compareTo(right);
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user