GH-1192 - Migrate code base to jSpecify for nullness verification.
This commit is contained in:
@@ -17,6 +17,11 @@
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jspecify</groupId>
|
||||
<artifactId>jspecify</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.modulith</groupId>
|
||||
<artifactId>spring-modulith-api</artifactId>
|
||||
|
||||
@@ -36,7 +36,7 @@ import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.modulith.core.Types.JMoleculesTypes;
|
||||
import org.springframework.modulith.core.Types.JavaTypes;
|
||||
import org.springframework.modulith.core.Types.SpringTypes;
|
||||
@@ -427,7 +427,7 @@ public class ApplicationModule implements Comparable<ApplicationModule> {
|
||||
|
||||
var candidatePackageName = PackageName.ofType(candidate);
|
||||
|
||||
return (candidatePackageName.isEmpty() || basePackage.getPackageName().contains(candidatePackageName))
|
||||
return (PackageName.isDefault(candidatePackageName) || basePackage.getPackageName().contains(candidatePackageName))
|
||||
&& getType(candidate).isPresent();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -172,7 +173,7 @@ public class ApplicationModuleDependencies {
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public ApplicationModule getModuleByType(String name) {
|
||||
public @Nullable ApplicationModule getModuleByType(String name) {
|
||||
|
||||
Assert.hasText(name, "Name must not be null or empty!");
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.Optional;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.modulith.ApplicationModule;
|
||||
import org.springframework.modulith.core.Types.JMoleculesTypes;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -240,7 +241,7 @@ public class ApplicationModuleSource {
|
||||
* @param delegates must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
private static ApplicationModuleSourceMetadata delegating(ApplicationModuleSourceMetadata... delegates) {
|
||||
private static ApplicationModuleSourceMetadata delegating(@Nullable ApplicationModuleSourceMetadata... delegates) {
|
||||
|
||||
return new ApplicationModuleSourceMetadata() {
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* SPI to allow build units contribute additional {@link ApplicationModuleSource}s in the form of either declaring them
|
||||
|
||||
@@ -32,9 +32,9 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.aot.generate.Generated;
|
||||
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.function.SingletonSupplier;
|
||||
@@ -61,7 +61,7 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
|
||||
private static final Map<CacheKey, ApplicationModules> CACHE = new ConcurrentHashMap<>();
|
||||
|
||||
private static final ImportOption IMPORT_OPTION = new ImportOption.DoNotIncludeTests();
|
||||
private static final DescribedPredicate<CanBeAnnotated> IS_GENERATED;
|
||||
private static final @Nullable DescribedPredicate<CanBeAnnotated> IS_GENERATED;
|
||||
private static final DescribedPredicate<HasName> IS_SPRING_CGLIB_PROXY = nameContaining("$$SpringCGLIB$$");
|
||||
|
||||
static {
|
||||
@@ -624,9 +624,9 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
|
||||
* {@literal null} or its type does not reside in any module.
|
||||
*
|
||||
* @param object can be {@literal null}.
|
||||
* @return
|
||||
* @return can be {@literal null}.
|
||||
*/
|
||||
private Integer getModuleIndexFor(@Nullable Object object) {
|
||||
private @Nullable Integer getModuleIndexFor(@Nullable Object object) {
|
||||
|
||||
return Optional.ofNullable(object)
|
||||
.map(it -> Class.class.isInstance(it) ? Class.class.cast(it) : it.getClass())
|
||||
@@ -778,7 +778,7 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
|
||||
if (obj == this) {
|
||||
return true;
|
||||
|
||||
@@ -20,6 +20,7 @@ import static org.springframework.modulith.core.Types.JavaXTypes.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -676,7 +677,9 @@ public abstract class ArchitecturallyEvidentType {
|
||||
var attributes = AnnotatedElementUtils.getMergedAnnotationAttributes(method.reflect(),
|
||||
SpringTypes.AT_EVENT_LISTENER, false, false);
|
||||
|
||||
return List.of(attributes.getClassArray("classes"));
|
||||
return attributes == null
|
||||
? Collections.emptyList()
|
||||
: List.of(attributes.getClassArray("classes"));
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -29,7 +29,7 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@@ -75,7 +75,12 @@ public interface ModulithMetadata {
|
||||
return SpringBootModulithMetadata.of(javaPackage);
|
||||
}
|
||||
|
||||
var className = candidates.iterator().next().getBeanClassName();
|
||||
var definition = candidates.iterator().next();
|
||||
var className = definition.getBeanClassName();
|
||||
|
||||
if (className == null) {
|
||||
throw new IllegalStateException("No bean class name found on BeanDefinition %s!".formatted(definition));
|
||||
}
|
||||
|
||||
return of(ClassUtils.resolveClassName(className, ModulithMetadata.class.getClassLoader()));
|
||||
}
|
||||
|
||||
@@ -298,6 +298,10 @@ public class NamedInterfaces implements Iterable<NamedInterface> {
|
||||
var annotation = AnnotatedElementUtils.getMergedAnnotation(it.reflect(),
|
||||
org.springframework.modulith.NamedInterface.class);
|
||||
|
||||
if (annotation == null) {
|
||||
throw new IllegalStateException("No @NamedInterface annotation found!");
|
||||
}
|
||||
|
||||
NamedInterface.getDefaultedNames(annotation, it.getPackageName())
|
||||
.forEach(name -> mappings.add(name, it));
|
||||
});
|
||||
|
||||
@@ -18,10 +18,11 @@ package org.springframework.modulith.core;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
@@ -34,6 +35,8 @@ import org.springframework.util.ClassUtils;
|
||||
*/
|
||||
public class PackageName implements Comparable<PackageName> {
|
||||
|
||||
public static final String DEFAULT = "<<default>>";
|
||||
|
||||
private static final Map<String, PackageName> PACKAGE_NAMES = new HashMap<>();
|
||||
|
||||
private final String name;
|
||||
@@ -77,6 +80,20 @@ public class PackageName implements Comparable<PackageName> {
|
||||
return PackageName.of(ClassUtils.getPackageName(fullyQualifiedName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link PackageName} for the given fully-qualified type name.
|
||||
*
|
||||
* @param fullyQualifiedName must not be {@literal null} or empty.
|
||||
* @return will never be {@literal null}.
|
||||
* @since 2.0
|
||||
*/
|
||||
public static PackageName ofType(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
return PackageName.of(ClassUtils.getPackageName(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link PackageName} with the given name.
|
||||
*
|
||||
@@ -88,7 +105,9 @@ public class PackageName implements Comparable<PackageName> {
|
||||
|
||||
Assert.notNull(name, "Name must not be null!");
|
||||
|
||||
return PACKAGE_NAMES.computeIfAbsent(name, PackageName::new);
|
||||
var defaulted = name.isBlank() ? DEFAULT : name;
|
||||
|
||||
return PACKAGE_NAMES.computeIfAbsent(defaulted, PackageName::new);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -107,6 +126,16 @@ public class PackageName implements Comparable<PackageName> {
|
||||
return PACKAGE_NAMES.computeIfAbsent(name, it -> new PackageName(name, segments));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link PackageName} is the default package name (logically an empty string).
|
||||
*
|
||||
* @param name must not be {@literal null}.
|
||||
* @since 2.0
|
||||
*/
|
||||
static boolean isDefault(PackageName name) {
|
||||
return name.hasName(DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the length of the package name.
|
||||
*
|
||||
@@ -281,7 +310,9 @@ public class PackageName implements Comparable<PackageName> {
|
||||
return Stream.of(reference);
|
||||
}
|
||||
|
||||
return Stream.concat(expandUntil(reference.getParent()), Stream.of(reference));
|
||||
var parent = Objects.requireNonNull(reference.getParent());
|
||||
|
||||
return Stream.concat(expandUntil(parent), Stream.of(reference));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -318,7 +349,7 @@ public class PackageName implements Comparable<PackageName> {
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
|
||||
if (obj == this) {
|
||||
return true;
|
||||
|
||||
@@ -21,8 +21,8 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.modulith.core.Types.SpringTypes;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
@@ -34,20 +34,21 @@ import org.springframework.util.Assert;
|
||||
*/
|
||||
class SpringBootModulithMetadata implements ModulithMetadata {
|
||||
|
||||
private static final Class<? extends Annotation> AT_SPRING_BOOT_APPLICATION = Types
|
||||
private static final @Nullable Class<? extends Annotation> AT_SPRING_BOOT_APPLICATION = Types
|
||||
.loadIfPresent(SpringTypes.AT_SPRING_BOOT_APPLICATION);
|
||||
|
||||
private final @NonNull Object source;
|
||||
private final String systemName, basePackage;
|
||||
private final Object source;
|
||||
private final String basePackage;
|
||||
private final @Nullable String systemName;
|
||||
|
||||
/**
|
||||
* Creates a new {@link SpringBootModulithMetadata} for the given source.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
* @param systemName can be {@literal null}.
|
||||
* @param basePackage must not be {@literal null}.
|
||||
* @param systemName can be {@literal null}.
|
||||
*/
|
||||
private SpringBootModulithMetadata(Object source, String systemName, String basePackage) {
|
||||
private SpringBootModulithMetadata(Object source, String basePackage, @Nullable String systemName) {
|
||||
|
||||
Assert.notNull(source, "Source must not be null!");
|
||||
Assert.notNull(basePackage, "Base package must not be null!");
|
||||
@@ -71,7 +72,7 @@ class SpringBootModulithMetadata implements ModulithMetadata {
|
||||
|
||||
return Optional.ofNullable(AT_SPRING_BOOT_APPLICATION) //
|
||||
.filter(it -> AnnotatedElementUtils.hasAnnotation(annotated, it)) //
|
||||
.map(__ -> new SpringBootModulithMetadata(annotated, annotated.getSimpleName(), annotated.getPackageName()));
|
||||
.map(__ -> new SpringBootModulithMetadata(annotated, annotated.getPackageName(), annotated.getSimpleName()));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,7 +85,7 @@ class SpringBootModulithMetadata implements ModulithMetadata {
|
||||
|
||||
Assert.hasText(javaPackage, "Package name must not be null or empty!");
|
||||
|
||||
return new SpringBootModulithMetadata(javaPackage, null, javaPackage);
|
||||
return new SpringBootModulithMetadata(javaPackage, javaPackage, null);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -26,7 +26,7 @@ import java.util.function.Predicate;
|
||||
import org.jmolecules.archunit.JMoleculesArchitectureRules;
|
||||
import org.jmolecules.archunit.JMoleculesDddRules;
|
||||
import org.jmolecules.ddd.annotation.Module;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.modulith.PackageInfo;
|
||||
import org.springframework.modulith.core.ApplicationModuleSource.ApplicationModuleSourceMetadata;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -82,7 +82,7 @@ public class Types {
|
||||
static final String AT_DOMAIN_EVENT = BASE_PACKAGE + ".event.annotation.DomainEvent";
|
||||
static final String DOMAIN_EVENT = BASE_PACKAGE + ".event.types.DomainEvent";
|
||||
|
||||
private static Collection<ArchRule> RULES;
|
||||
private static @Nullable Collection<ArchRule> RULES;
|
||||
|
||||
/**
|
||||
* Returns whether jMolecules is generally present.
|
||||
@@ -123,34 +123,38 @@ public class Types {
|
||||
*/
|
||||
public static Collection<ArchRule> getRules() {
|
||||
|
||||
if (RULES == null) {
|
||||
var rules = RULES;
|
||||
|
||||
if (rules == null) {
|
||||
|
||||
var classLoader = JMoleculesTypes.class.getClassLoader();
|
||||
RULES = new ArrayList<ArchRule>();
|
||||
rules = new ArrayList<ArchRule>();
|
||||
|
||||
if (ClassUtils.isPresent(DDD_RULES, classLoader)) {
|
||||
RULES.add(JMoleculesDddRules.all());
|
||||
rules.add(JMoleculesDddRules.all());
|
||||
}
|
||||
|
||||
if (!ClassUtils.isPresent(ARCHITECTURE_RULES, classLoader)) {
|
||||
return RULES;
|
||||
return rules;
|
||||
}
|
||||
|
||||
if (ClassUtils.isPresent(HEXAGONAL, classLoader)) {
|
||||
RULES.add(JMoleculesArchitectureRules.ensureHexagonal());
|
||||
rules.add(JMoleculesArchitectureRules.ensureHexagonal());
|
||||
}
|
||||
|
||||
if (ClassUtils.isPresent(LAYERED, classLoader)) {
|
||||
RULES.add(JMoleculesArchitectureRules.ensureLayering());
|
||||
rules.add(JMoleculesArchitectureRules.ensureLayering());
|
||||
}
|
||||
|
||||
if (ClassUtils.isPresent(ONION, classLoader)) {
|
||||
RULES.add(JMoleculesArchitectureRules.ensureOnionClassical());
|
||||
RULES.add(JMoleculesArchitectureRules.ensureOnionSimple());
|
||||
rules.add(JMoleculesArchitectureRules.ensureOnionClassical());
|
||||
rules.add(JMoleculesArchitectureRules.ensureOnionSimple());
|
||||
}
|
||||
|
||||
RULES = rules;
|
||||
}
|
||||
|
||||
return RULES;
|
||||
return rules;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Core configuration abstractions of Spring Modulith.
|
||||
*/
|
||||
@org.springframework.lang.NonNullApi
|
||||
@org.jspecify.annotations.NullMarked
|
||||
package org.springframework.modulith.core.config;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Core, internal abstractions of Spring Modulith.
|
||||
*/
|
||||
@org.springframework.lang.NonNullApi
|
||||
@org.jspecify.annotations.NullMarked
|
||||
package org.springframework.modulith.core;
|
||||
|
||||
@@ -80,4 +80,12 @@ class ApplicationModulesUnitTests {
|
||||
it -> assertThat(it).contains("Invalid", "'invalid'", "'ni.nested.b.first'"),
|
||||
it -> assertThat(it).contains("Invalid", "'ni'", "'ni.nested.b.first'"));
|
||||
}
|
||||
|
||||
@Test // GH-1192
|
||||
void findsTypeBySimpleName() {
|
||||
|
||||
assertThat(modules.getModuleByName("ni")).hasValueSatisfying(it -> {
|
||||
assertThat(it.contains("RootType")).isTrue();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user