GH-1100 - Fix performance regression in JavaPackage.
The fix for GH-1041 introduced a performance regression as the calculation of the sub-packages escaped the SingletonSupplier and thus is now included in every hashCode() calculation. Took the chance to significantly revamp the sub-package calculation for nested packages. In other words, the entire sub-package arrangement for a package is calculated once with pre-computed intermediaries held instead of re-computing them per sub-package.
This commit is contained in:
@@ -18,16 +18,18 @@ package org.springframework.modulith.core;
|
||||
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.*;
|
||||
import static com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates.*;
|
||||
import static com.tngtech.archunit.core.domain.properties.HasModifiers.Predicates.*;
|
||||
import static java.util.stream.Collectors.*;
|
||||
import static org.springframework.modulith.core.SyntacticSugar.*;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -57,8 +59,8 @@ public class JavaPackage implements DescribedIterable<JavaClass>, Comparable<Jav
|
||||
has(simpleName(PACKAGE_INFO_NAME)).or(is(metaAnnotatedWith(PackageInfo.class)));
|
||||
|
||||
private final PackageName name;
|
||||
private final Classes classes, packageClasses;
|
||||
private final Supplier<Set<JavaPackage>> directSubPackages;
|
||||
private final Classes classes;
|
||||
private final Supplier<SortedSet<JavaPackage>> directSubPackages;
|
||||
private final Supplier<JavaPackages> subPackages;
|
||||
|
||||
/**
|
||||
@@ -66,23 +68,35 @@ public class JavaPackage implements DescribedIterable<JavaClass>, Comparable<Jav
|
||||
*
|
||||
* @param classes must not be {@literal null}.
|
||||
* @param name must not be {@literal null}.
|
||||
* @param includeSubPackages
|
||||
* @param includeSubPackages whether to include sub-packages.
|
||||
*/
|
||||
private JavaPackage(Classes classes, PackageName name, boolean includeSubPackages) {
|
||||
|
||||
this(classes.that(resideInAPackage(name.asFilter(includeSubPackages))), name, includeSubPackages
|
||||
? SingletonSupplier.of(() -> detectSubPackages(classes, name))
|
||||
: SingletonSupplier.of(JavaPackages.NONE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link JavaPackage} for the given {@link Classes}, name and pre-computed sub-packages.
|
||||
*
|
||||
* @param classes must not be {@literal null}.
|
||||
* @param name must not be {@literal null}.
|
||||
* @param subpackages must not be {@literal null}.
|
||||
* @see #detectSubPackages(Classes, PackageName)
|
||||
*/
|
||||
private JavaPackage(Classes classes, PackageName name, Supplier<JavaPackages> subpackages) {
|
||||
|
||||
Assert.notNull(classes, "Classes must not be null!");
|
||||
Assert.notNull(name, "PackageName must not be null!");
|
||||
Assert.notNull(subpackages, "Sub-packages must not be null!");
|
||||
|
||||
this.classes = classes;
|
||||
this.packageClasses = classes
|
||||
.that(resideInAPackage(name.asFilter(includeSubPackages)));
|
||||
this.classes = classes.that(resideInAPackage(name.asFilter(true)));
|
||||
this.name = name;
|
||||
|
||||
this.directSubPackages = () -> detectSubPackages()
|
||||
this.subPackages = subpackages;
|
||||
this.directSubPackages = SingletonSupplier.of(() -> subPackages.get().stream()
|
||||
.filter(this::isDirectParentOf)
|
||||
.collect(Collectors.toUnmodifiableSet());
|
||||
|
||||
this.subPackages = SingletonSupplier.of(() -> detectSubPackages()
|
||||
.collect(collectingAndThen(toUnmodifiableList(), JavaPackages::new)));
|
||||
.collect(Collectors.toCollection(TreeSet::new)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -166,7 +180,7 @@ public class JavaPackage implements DescribedIterable<JavaClass>, Comparable<Jav
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public Classes getClasses() {
|
||||
return packageClasses;
|
||||
return classes;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,7 +190,7 @@ public class JavaPackage implements DescribedIterable<JavaClass>, Comparable<Jav
|
||||
*/
|
||||
public Classes getExposedClasses() {
|
||||
|
||||
return packageClasses //
|
||||
return classes //
|
||||
.that(doNotHave(simpleName(PACKAGE_INFO_NAME))) //
|
||||
.that(have(modifier(JavaModifier.PUBLIC)));
|
||||
}
|
||||
@@ -191,7 +205,7 @@ public class JavaPackage implements DescribedIterable<JavaClass>, Comparable<Jav
|
||||
|
||||
Assert.notNull(annotation, "Annotation must not be null!");
|
||||
|
||||
return packageClasses.that(ARE_PACKAGE_INFOS.and(are(metaAnnotatedWith(annotation)))).stream() //
|
||||
return classes.that(ARE_PACKAGE_INFOS.and(are(metaAnnotatedWith(annotation)))).stream() //
|
||||
.map(JavaClass::getPackageName) //
|
||||
.filter(Predicate.not(name::hasName))
|
||||
.distinct() //
|
||||
@@ -208,7 +222,7 @@ public class JavaPackage implements DescribedIterable<JavaClass>, Comparable<Jav
|
||||
|
||||
Assert.notNull(predicate, "Predicate must not be null!");
|
||||
|
||||
return packageClasses.that(predicate);
|
||||
return classes.that(predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,7 +234,7 @@ public class JavaPackage implements DescribedIterable<JavaClass>, Comparable<Jav
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
return packageClasses.contains(type);
|
||||
return classes.contains(type);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,7 +246,7 @@ public class JavaPackage implements DescribedIterable<JavaClass>, Comparable<Jav
|
||||
|
||||
Assert.hasText(typeName, "Type name must not be null or empty!");
|
||||
|
||||
return packageClasses.contains(typeName);
|
||||
return classes.contains(typeName);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,7 +255,7 @@ public class JavaPackage implements DescribedIterable<JavaClass>, Comparable<Jav
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public Stream<JavaClass> stream() {
|
||||
return packageClasses.stream();
|
||||
return classes.stream();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -255,7 +269,7 @@ public class JavaPackage implements DescribedIterable<JavaClass>, Comparable<Jav
|
||||
|
||||
var isPackageInfo = have(simpleName(PACKAGE_INFO_NAME)).or(are(metaAnnotatedWith(PackageInfo.class)));
|
||||
|
||||
return packageClasses.that(isPackageInfo.and(are(metaAnnotatedWith(annotationType)))) //
|
||||
return classes.that(isPackageInfo.and(are(metaAnnotatedWith(annotationType)))) //
|
||||
.toOptional() //
|
||||
.map(it -> it.reflect())
|
||||
.map(it -> AnnotatedElementUtils.getMergedAnnotation(it, annotationType));
|
||||
@@ -325,7 +339,7 @@ public class JavaPackage implements DescribedIterable<JavaClass>, Comparable<Jav
|
||||
.map(JavaPackage::asFilter)
|
||||
.toArray(String[]::new);
|
||||
|
||||
return packageClasses.that(resideOutsideOfPackages(excludedPackages));
|
||||
return classes.that(resideOutsideOfPackages(excludedPackages));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -369,7 +383,7 @@ public class JavaPackage implements DescribedIterable<JavaClass>, Comparable<Jav
|
||||
|
||||
var isPackageInfo = have(simpleName(PACKAGE_INFO_NAME)).or(are(metaAnnotatedWith(PackageInfo.class)));
|
||||
|
||||
var annotatedTypes = toSingle().packageClasses
|
||||
var annotatedTypes = toSingle().classes
|
||||
.that(isPackageInfo.and(are(metaAnnotatedWith(annotationType))))
|
||||
.stream()
|
||||
.map(JavaClass::reflect)
|
||||
@@ -409,17 +423,6 @@ public class JavaPackage implements DescribedIterable<JavaClass>, Comparable<Jav
|
||||
return classes.getDescription();
|
||||
}
|
||||
|
||||
private Stream<JavaPackage> detectSubPackages() {
|
||||
|
||||
return packageClasses.stream() //
|
||||
.map(JavaClass::getPackageName)
|
||||
.filter(Predicate.not(name::hasName))
|
||||
.map(PackageName::of)
|
||||
.flatMap(name::expandUntil)
|
||||
.distinct()
|
||||
.map(it -> new JavaPackage(classes, it, true));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Iterable#iterator()
|
||||
@@ -469,8 +472,7 @@ public class JavaPackage implements DescribedIterable<JavaClass>, Comparable<Jav
|
||||
|
||||
return Objects.equals(this.classes, that.classes) //
|
||||
&& Objects.equals(this.getDirectSubPackages(), that.getDirectSubPackages()) //
|
||||
&& Objects.equals(this.name, that.name) //
|
||||
&& Objects.equals(this.packageClasses, that.packageClasses);
|
||||
&& Objects.equals(this.name, that.name);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -479,10 +481,40 @@ public class JavaPackage implements DescribedIterable<JavaClass>, Comparable<Jav
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(classes, directSubPackages.get(), name, packageClasses);
|
||||
return Objects.hash(classes, directSubPackages.get(), name);
|
||||
}
|
||||
|
||||
static Comparator<JavaPackage> reverse() {
|
||||
return (left, right) -> -left.compareTo(right);
|
||||
}
|
||||
|
||||
private static JavaPackages detectSubPackages(Classes classes, PackageName name) {
|
||||
|
||||
var packages = new TreeSet<PackageName>(Comparator.reverseOrder());
|
||||
|
||||
for (JavaClass clazz : classes) {
|
||||
|
||||
var candidate = PackageName.of(clazz.getPackageName());
|
||||
|
||||
if (candidate.equals(name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
name.expandUntil(candidate).forEach(packages::add);
|
||||
}
|
||||
|
||||
var result = new TreeMap<PackageName, JavaPackage>();
|
||||
|
||||
for (PackageName packageName : packages) {
|
||||
|
||||
Supplier<JavaPackages> subPackages = () -> result.entrySet().stream()
|
||||
.filter(it -> it.getKey().isSubPackageOf(packageName))
|
||||
.map(Entry::getValue)
|
||||
.collect(Collectors.collectingAndThen(Collectors.toList(), JavaPackages::new));
|
||||
|
||||
result.put(packageName, new JavaPackage(classes, packageName, SingletonSupplier.of(subPackages)));
|
||||
}
|
||||
|
||||
return new JavaPackages(result.values());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user