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:
Oliver Drotbohm
2025-03-06 17:42:32 +01:00
parent 083ce7beae
commit df4d82dd3f
3 changed files with 97 additions and 12 deletions

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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");
}
}