GH-14 - Remove Lombok from production sources.
Polished a lot of Javadoc.
This commit is contained in:
@@ -15,9 +15,6 @@
|
||||
*/
|
||||
package org.springframework.modulith.model;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -33,12 +30,26 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
class AnnotationModulithMetadata implements ModulithMetadata {
|
||||
|
||||
private final Class<?> modulithType;
|
||||
private final Modulithic annotation;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AnnotationModulithMetadata} for the given type and annotation.
|
||||
*
|
||||
* @param modulithType must not be {@literal null}.
|
||||
* @param annotation must not be {@literal null}.
|
||||
*/
|
||||
private AnnotationModulithMetadata(Class<?> modulithType, Modulithic annotation) {
|
||||
|
||||
Assert.notNull(modulithType, "Type must not be null!");
|
||||
Assert.notNull(annotation, "Annotation must not be null!");
|
||||
|
||||
this.modulithType = modulithType;
|
||||
this.annotation = annotation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ModulithMetadata} inspecting {@link Modulithic} annotation or return {@link Optional#empty()} if
|
||||
* the type given does not carry the annotation.
|
||||
@@ -62,6 +73,15 @@ class AnnotationModulithMetadata implements ModulithMetadata {
|
||||
*/
|
||||
@Override
|
||||
public Object getModulithSource() {
|
||||
return getSource();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.modulith.model.ModulithMetadata#getModulithSource()
|
||||
*/
|
||||
@Override
|
||||
public Object getSource() {
|
||||
return modulithType;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,18 +23,11 @@ import static org.springframework.modulith.model.Types.JavaXTypes.*;
|
||||
import static org.springframework.modulith.model.Types.SpringDataTypes.*;
|
||||
import static org.springframework.modulith.model.Types.SpringTypes.*;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -63,20 +56,19 @@ import com.tngtech.archunit.thirdparty.com.google.common.base.Suppliers;
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@EqualsAndHashCode(doNotUseGetters = true)
|
||||
public class ApplicationModule {
|
||||
|
||||
/**
|
||||
* The base package of the {@link ApplicationModule}.
|
||||
*/
|
||||
private final @Getter JavaPackage basePackage;
|
||||
private final JavaPackage basePackage;
|
||||
private final ApplicationModuleInformation information;
|
||||
|
||||
/**
|
||||
* All {@link NamedInterfaces} of the {@link ApplicationModule} either declared explicitly via {@link NamedInterface}
|
||||
* or implicitly.
|
||||
*/
|
||||
private final @Getter NamedInterfaces namedInterfaces;
|
||||
private final NamedInterfaces namedInterfaces;
|
||||
private final boolean useFullyQualifiedModuleNames;
|
||||
|
||||
private final Supplier<Classes> springBeans;
|
||||
@@ -104,6 +96,24 @@ public class ApplicationModule {
|
||||
this.publishedEvents = Suppliers.memoize(() -> findPublishedEvents());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the module's base package.
|
||||
*
|
||||
* @return the basePackage
|
||||
*/
|
||||
public JavaPackage getBasePackage() {
|
||||
return basePackage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all {@link NamedInterfaces} exposed by the module.
|
||||
*
|
||||
* @return the namedInterfaces will never be {@literal null}.
|
||||
*/
|
||||
public NamedInterfaces getNamedInterfaces() {
|
||||
return namedInterfaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the logical name of the module.
|
||||
*
|
||||
@@ -407,6 +417,41 @@ public class ApplicationModule {
|
||||
return getType(candidate).isPresent();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(obj instanceof ApplicationModule that)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Objects.equals(this.basePackage, that.basePackage) //
|
||||
&& Objects.equals(this.entities, that.entities) //
|
||||
&& Objects.equals(this.information, that.information) //
|
||||
&& Objects.equals(this.namedInterfaces, that.namedInterfaces) //
|
||||
&& Objects.equals(this.publishedEvents, that.publishedEvents) //
|
||||
&& Objects.equals(this.springBeans, that.springBeans) //
|
||||
&& Objects.equals(this.useFullyQualifiedModuleNames, that.useFullyQualifiedModuleNames) //
|
||||
&& Objects.equals(this.valueTypes, that.valueTypes);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(basePackage, entities, information, namedInterfaces, publishedEvents, springBeans,
|
||||
useFullyQualifiedModuleNames, valueTypes);
|
||||
}
|
||||
|
||||
private List<EventType> findPublishedEvents() {
|
||||
|
||||
DescribedPredicate<JavaClass> isEvent = implement(JMoleculesTypes.DOMAIN_EVENT) //
|
||||
@@ -529,15 +574,28 @@ public class ApplicationModule {
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Value
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
static class DeclaredDependency {
|
||||
|
||||
private static final String INVALID_EXPLICIT_MODULE_DEPENDENCY = "Invalid explicit module dependency in %s! No module found with name '%s'.";
|
||||
private static final String INVALID_NAMED_INTERFACE_DECLARATION = "No named interface named '%s' found! Original dependency declaration: %s -> %s.";
|
||||
|
||||
@NonNull ApplicationModule target;
|
||||
@NonNull NamedInterface namedInterface;
|
||||
private final ApplicationModule target;
|
||||
private final NamedInterface namedInterface;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DeclaredDependency} for the given {@link ApplicationModule} and {@link NamedInterface}.
|
||||
*
|
||||
* @param target must not be {@literal null}.
|
||||
* @param namedInterface must not be {@literal null}.
|
||||
*/
|
||||
private DeclaredDependency(ApplicationModule target, NamedInterface namedInterface) {
|
||||
|
||||
Assert.notNull(target, "Target ApplicationModule must not be null!");
|
||||
Assert.notNull(namedInterface, "NamedInterface must not be null!");
|
||||
|
||||
this.target = target;
|
||||
this.namedInterface = namedInterface;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link DeclaredDependency} to the module and optionally named interface defined by the given
|
||||
@@ -580,10 +638,22 @@ public class ApplicationModule {
|
||||
* @return
|
||||
*/
|
||||
public static DeclaredDependency to(ApplicationModule module) {
|
||||
|
||||
Assert.notNull(module, "ApplicationModule must not be null!");
|
||||
|
||||
return new DeclaredDependency(module, module.getNamedInterfaces().getUnnamedInterface());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the {@link DeclaredDependency} contains the given {@link JavaClass}.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public boolean contains(JavaClass type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
return namedInterface.contains(type);
|
||||
}
|
||||
|
||||
@@ -595,6 +665,35 @@ public class ApplicationModule {
|
||||
public String toString() {
|
||||
return namedInterface.isUnnamed() ? target.getName() : target.getName() + "::" + namedInterface.getName();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(obj instanceof DeclaredDependency that)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Objects.equals(this.target, that.target) //
|
||||
&& Objects.equals(this.namedInterface, that.namedInterface);
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(target, namedInterface);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -602,16 +701,26 @@ public class ApplicationModule {
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@Value
|
||||
static class DeclaredDependencies {
|
||||
|
||||
List<DeclaredDependency> dependencies;
|
||||
private final List<DeclaredDependency> dependencies;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DeclaredDependencies} for the given {@link List} of {@link DeclaredDependency}.
|
||||
*
|
||||
* @param dependencies must not be {@literal null}.
|
||||
*/
|
||||
public DeclaredDependencies(List<DeclaredDependency> dependencies) {
|
||||
|
||||
Assert.notNull(dependencies, "Dependencies must not be null!");
|
||||
|
||||
this.dependencies = dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether any of the dependencies contains the given {@link JavaClass}.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public boolean contains(JavaClass type) {
|
||||
|
||||
@@ -621,10 +730,17 @@ public class ApplicationModule {
|
||||
.anyMatch(it -> it.contains(type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the {@link DeclaredDependencies} are empty.
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
return dependencies.isEmpty();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
@@ -632,18 +748,64 @@ public class ApplicationModule {
|
||||
.map(DeclaredDependency::toString)
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(obj instanceof DeclaredDependencies that)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Objects.equals(this.dependencies, that.dependencies);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(dependencies);
|
||||
}
|
||||
}
|
||||
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor
|
||||
static class QualifiedDependency {
|
||||
|
||||
private static final List<String> INJECTION_TYPES = Arrays.asList(//
|
||||
AT_AUTOWIRED, AT_RESOURCE, AT_INJECT);
|
||||
private static final List<String> INJECTION_TYPES = Arrays.asList(AT_AUTOWIRED, AT_RESOURCE, AT_INJECT);
|
||||
|
||||
private final @NonNull @Getter JavaClass source, target;
|
||||
private final @NonNull String description;
|
||||
private final @NonNull DependencyType type;
|
||||
private final JavaClass source, target;
|
||||
private final String description;
|
||||
private final DependencyType type;
|
||||
|
||||
/**
|
||||
* Creates a new {@link QualifiedDependency} from the given source and target {@link JavaClass}, description and
|
||||
* {@link DependencyType}.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
* @param target must not be {@literal null}.
|
||||
* @param description must not be {@literal null}.
|
||||
* @param type must not be {@literal null}.
|
||||
*/
|
||||
public QualifiedDependency(JavaClass source, JavaClass target, String description, DependencyType type) {
|
||||
|
||||
Assert.notNull(source, "Source JavaClass must not be null!");
|
||||
Assert.notNull(target, "Target JavaClass must not be null!");
|
||||
Assert.notNull(description, "Description must not be null!");
|
||||
Assert.notNull(type, "DependencyType must not be null!");
|
||||
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
this.description = description;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
QualifiedDependency(Dependency dependency) {
|
||||
this(dependency.getOriginClass(), //
|
||||
@@ -652,6 +814,65 @@ public class ApplicationModule {
|
||||
DependencyType.forDependency(dependency));
|
||||
}
|
||||
|
||||
static QualifiedDependency fromCodeUnitParameter(JavaCodeUnit codeUnit, JavaClass parameter) {
|
||||
|
||||
var description = createDescription(codeUnit, parameter, "parameter");
|
||||
var type = DependencyType.forCodeUnit(codeUnit) //
|
||||
.defaultOr(() -> DependencyType.forParameter(parameter));
|
||||
|
||||
return new QualifiedDependency(codeUnit.getOwner(), parameter, description, type);
|
||||
}
|
||||
|
||||
static QualifiedDependency fromCodeUnitReturnType(JavaCodeUnit codeUnit) {
|
||||
|
||||
var description = createDescription(codeUnit, codeUnit.getRawReturnType(), "return type");
|
||||
|
||||
return new QualifiedDependency(codeUnit.getOwner(), codeUnit.getRawReturnType(), description,
|
||||
DependencyType.DEFAULT);
|
||||
}
|
||||
|
||||
static Stream<QualifiedDependency> fromType(ArchitecturallyEvidentType type) {
|
||||
|
||||
var source = type.getType();
|
||||
|
||||
return Stream.concat(Stream.concat(fromConstructorOf(type), fromMethodsOf(source)), fromFieldsOf(source));
|
||||
}
|
||||
|
||||
static Stream<QualifiedDependency> allFrom(JavaCodeUnit codeUnit) {
|
||||
|
||||
var parameterDependencies = codeUnit.getRawParameterTypes()//
|
||||
.stream() //
|
||||
.map(it -> fromCodeUnitParameter(codeUnit, it));
|
||||
|
||||
var returnType = Stream.of(fromCodeUnitReturnType(codeUnit));
|
||||
|
||||
return Stream.concat(parameterDependencies, returnType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the source {@link JavaClass}.
|
||||
*
|
||||
* @return the source will never be {@literal null}.
|
||||
*/
|
||||
public JavaClass getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the target {@link JavaClass}.
|
||||
*
|
||||
* @return the target must not be {@literal null}.
|
||||
*/
|
||||
public JavaClass getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the {@link QualifiedDependency} has the given {@link DependencyType}.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
boolean hasType(DependencyType type) {
|
||||
return this.type.equals(type);
|
||||
}
|
||||
@@ -704,39 +925,34 @@ public class ApplicationModule {
|
||||
return type.format(FormatableType.of(source), FormatableType.of(target));
|
||||
}
|
||||
|
||||
static QualifiedDependency fromCodeUnitParameter(JavaCodeUnit codeUnit, JavaClass parameter) {
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
var description = createDescription(codeUnit, parameter, "parameter");
|
||||
var type = DependencyType.forCodeUnit(codeUnit) //
|
||||
.defaultOr(() -> DependencyType.forParameter(parameter));
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return new QualifiedDependency(codeUnit.getOwner(), parameter, description, type);
|
||||
if (!(obj instanceof QualifiedDependency other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Objects.equals(this.source, other.source) //
|
||||
&& Objects.equals(this.target, other.target) //
|
||||
&& Objects.equals(this.description, other.description) //
|
||||
&& Objects.equals(this.type, other.type); //
|
||||
}
|
||||
|
||||
static QualifiedDependency fromCodeUnitReturnType(JavaCodeUnit codeUnit) {
|
||||
|
||||
var description = createDescription(codeUnit, codeUnit.getRawReturnType(), "return type");
|
||||
|
||||
return new QualifiedDependency(codeUnit.getOwner(), codeUnit.getRawReturnType(), description,
|
||||
DependencyType.DEFAULT);
|
||||
}
|
||||
|
||||
static Stream<QualifiedDependency> fromType(ArchitecturallyEvidentType type) {
|
||||
|
||||
var source = type.getType();
|
||||
|
||||
return Stream.concat(Stream.concat(fromConstructorOf(type), fromMethodsOf(source)), fromFieldsOf(source));
|
||||
}
|
||||
|
||||
static Stream<QualifiedDependency> allFrom(JavaCodeUnit codeUnit) {
|
||||
|
||||
var parameterDependencies = codeUnit.getRawParameterTypes()//
|
||||
.stream() //
|
||||
.map(it -> fromCodeUnitParameter(codeUnit, it));
|
||||
|
||||
var returnType = Stream.of(fromCodeUnitReturnType(codeUnit));
|
||||
|
||||
return Stream.concat(parameterDependencies, returnType);
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(source, target, description, type);
|
||||
}
|
||||
|
||||
private static Stream<QualifiedDependency> fromConstructorOf(ArchitecturallyEvidentType source) {
|
||||
@@ -885,15 +1101,35 @@ public class ApplicationModule {
|
||||
}
|
||||
}
|
||||
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
private static class DefaultApplicationModuleDependency implements ApplicationModuleDependency {
|
||||
|
||||
private final QualifiedDependency dependency;
|
||||
private final ApplicationModule target;
|
||||
|
||||
static Stream<DefaultApplicationModuleDependency> of(QualifiedDependency dependency, ApplicationModules modules) {
|
||||
/**
|
||||
* Creates a new {@link ApplicationModuleDependency} for the given {@link QualifiedDependency} and
|
||||
* {@link ApplicationModules}.
|
||||
*
|
||||
* @param dependency must not be {@literal null}.
|
||||
* @param target must not be {@literal null}.
|
||||
*/
|
||||
private DefaultApplicationModuleDependency(QualifiedDependency dependency, ApplicationModule target) {
|
||||
|
||||
Assert.notNull(dependency, "QualifiedDependency must not be null!");
|
||||
Assert.notNull(target, "Target ApplicationModule must not be null!");
|
||||
|
||||
this.dependency = dependency;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link Stream} of {@link ApplicationModuleDependency} for the given {@link QualifiedDependency} and
|
||||
* {@link ApplicationModules}.
|
||||
*
|
||||
* @param dependency must not be {@literal null}.
|
||||
* @param modules must not be {@literal null}.
|
||||
*/
|
||||
static Stream<ApplicationModuleDependency> of(QualifiedDependency dependency, ApplicationModules modules) {
|
||||
|
||||
return modules.getModuleByType(dependency.getTarget()).stream()
|
||||
.map(it -> new DefaultApplicationModuleDependency(dependency, it));
|
||||
@@ -934,5 +1170,42 @@ public class ApplicationModule {
|
||||
public ApplicationModule getTargetModule() {
|
||||
return target;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DefaultApplicationModuleDependency [dependency=" + dependency + ", target=" + target + "]";
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(obj instanceof DefaultApplicationModuleDependency other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Objects.equals(this.target, other.target) //
|
||||
&& Objects.equals(this.dependency, other.dependency);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(target, dependency);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
*/
|
||||
package org.springframework.modulith.model;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
@@ -25,16 +23,45 @@ import java.util.stream.Stream;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* The materialized, in other words actually present dependencies of the current module towards other modules.
|
||||
* The materialized, in other words actually present, dependencies of the current module towards other modules.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@RequiredArgsConstructor(staticName = "of")
|
||||
public class ApplicationModuleDependencies {
|
||||
|
||||
private final List<ApplicationModuleDependency> dependencies;
|
||||
private final ApplicationModules modules;
|
||||
|
||||
/**
|
||||
* Creates a new {@link ApplicationModuleDependencies} for the given {@link List} of
|
||||
* {@link ApplicationModuleDependency} and {@link ApplicationModules}.
|
||||
*
|
||||
* @param dependencies must not be {@literal null}.
|
||||
* @param modules must not be {@literal null}.
|
||||
*/
|
||||
private ApplicationModuleDependencies(List<ApplicationModuleDependency> dependencies, ApplicationModules modules) {
|
||||
|
||||
Assert.notNull(dependencies, "ApplicationModuleDependency list must not be null!");
|
||||
Assert.notNull(modules, "ApplicationModules must not be null!");
|
||||
|
||||
this.dependencies = dependencies;
|
||||
this.modules = modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ApplicationModuleDependencies} for the given {@link List} of
|
||||
* {@link ApplicationModuleDependency} and {@link ApplicationModules}.
|
||||
*
|
||||
* @param dependencies must not be {@literal null}.
|
||||
* @param modules must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
static ApplicationModuleDependencies of(List<ApplicationModuleDependency> dependencies,
|
||||
ApplicationModules modules) {
|
||||
|
||||
return new ApplicationModuleDependencies(dependencies, modules);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the dependencies contain the given {@link ApplicationModule}.
|
||||
*
|
||||
|
||||
@@ -19,12 +19,6 @@ import static com.tngtech.archunit.base.DescribedPredicate.*;
|
||||
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.*;
|
||||
import static java.util.stream.Collectors.*;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Getter;
|
||||
import lombok.Value;
|
||||
import lombok.With;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -60,7 +54,6 @@ import com.tngtech.archunit.library.dependencies.SlicesRuleDefinition;
|
||||
* @author Oliver Drotbohm
|
||||
* @author Peter Gafert
|
||||
*/
|
||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class ApplicationModules implements Iterable<ApplicationModule> {
|
||||
|
||||
private static final Map<CacheKey, ApplicationModules> CACHE = new HashMap<>();
|
||||
@@ -90,7 +83,7 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
|
||||
private final Map<String, ApplicationModule> modules;
|
||||
private final JavaClasses allClasses;
|
||||
private final List<JavaPackage> rootPackages;
|
||||
private final @With(AccessLevel.PRIVATE) @Getter Set<ApplicationModule> sharedModules;
|
||||
private final Set<ApplicationModule> sharedModules;
|
||||
private final List<String> orderedNames;
|
||||
|
||||
private boolean verified;
|
||||
@@ -123,6 +116,39 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
|
||||
: modules.values().stream().map(ApplicationModule::getName).toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ApplicationModules} for the given {@link ModulithMetadata}, {@link ApplicationModule}s,
|
||||
* {@link JavaClasses}, {@link JavaPackage}s, shared {@link ApplicationModule}s, ordered module names and verified
|
||||
* flag.
|
||||
*
|
||||
* @param metadata must not be {@literal null}.
|
||||
* @param modules must not be {@literal null}.
|
||||
* @param allClasses must not be {@literal null}.
|
||||
* @param rootPackages must not be {@literal null}.
|
||||
* @param sharedModules must not be {@literal null}.
|
||||
* @param orderedNames must not be {@literal null}.
|
||||
* @param verified
|
||||
*/
|
||||
private ApplicationModules(ModulithMetadata metadata, Map<String, ApplicationModule> modules, JavaClasses classes,
|
||||
List<JavaPackage> rootPackages, Set<ApplicationModule> sharedModules, List<String> orderedNames,
|
||||
boolean verified) {
|
||||
|
||||
Assert.notNull(metadata, "ModulithMetadata must not be null!");
|
||||
Assert.notNull(modules, "Application modules must not be null!");
|
||||
Assert.notNull(classes, "JavaClasses must not be null!");
|
||||
Assert.notNull(rootPackages, "Root JavaPackages must not be null!");
|
||||
Assert.notNull(sharedModules, "Shared ApplicationModules must not be null!");
|
||||
Assert.notNull(orderedNames, "Ordered application module names must not be null!");
|
||||
|
||||
this.metadata = metadata;
|
||||
this.modules = modules;
|
||||
this.allClasses = classes;
|
||||
this.rootPackages = rootPackages;
|
||||
this.sharedModules = sharedModules;
|
||||
this.orderedNames = orderedNames;
|
||||
this.verified = verified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ApplicationModules} relative to the given modulith type. Will inspect the {@link Modulith}
|
||||
* annotation on the class given for advanced customizations of the module setup.
|
||||
@@ -147,7 +173,7 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
|
||||
*/
|
||||
public static ApplicationModules of(Class<?> modulithType, DescribedPredicate<JavaClass> ignored) {
|
||||
|
||||
CacheKey key = TypeKey.of(modulithType, ignored);
|
||||
CacheKey key = new TypeKey(modulithType, ignored);
|
||||
|
||||
return CACHE.computeIfAbsent(key, it -> {
|
||||
|
||||
@@ -177,7 +203,7 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
|
||||
*/
|
||||
public static ApplicationModules of(String javaPackage, DescribedPredicate<JavaClass> ignored) {
|
||||
|
||||
CacheKey key = PackageKey.of(javaPackage, ignored);
|
||||
CacheKey key = new PackageKey(javaPackage, ignored);
|
||||
|
||||
return CACHE.computeIfAbsent(key, it -> {
|
||||
|
||||
@@ -189,33 +215,32 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ApplicationModules} instance for the given {@link CacheKey}.
|
||||
* Returns the source of the {@link ApplicationModules}. Either a main application class or a package name.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
* @deprecated use {@link #getSource()} instead
|
||||
*/
|
||||
private static ApplicationModules of(CacheKey key) {
|
||||
|
||||
Assert.notNull(key, "Cache key must not be null!");
|
||||
|
||||
ModulithMetadata metadata = key.getMetadata();
|
||||
|
||||
Set<String> basePackages = new HashSet<>();
|
||||
basePackages.add(key.getBasePackage());
|
||||
basePackages.addAll(metadata.getAdditionalPackages());
|
||||
|
||||
ApplicationModules modules = new ApplicationModules(metadata, basePackages, key.getIgnored(),
|
||||
metadata.useFullyQualifiedModuleNames(), IMPORT_OPTION);
|
||||
|
||||
Set<ApplicationModule> sharedModules = metadata.getSharedModuleNames() //
|
||||
.map(modules::getRequiredModule) //
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
return modules.withSharedModules(sharedModules);
|
||||
@Deprecated(forRemoval = true)
|
||||
public Object getModulithSource() {
|
||||
return metadata.getSource();
|
||||
}
|
||||
|
||||
public Object getModulithSource() {
|
||||
return metadata.getModulithSource();
|
||||
/**
|
||||
* Returns the source of the {@link ApplicationModules}. Either a main application class or a package name.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public Object getSource() {
|
||||
return metadata.getSource();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all {@link ApplicationModule}s registered as shared ones.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public Set<ApplicationModule> getSharedModules() {
|
||||
return sharedModules;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -421,9 +446,13 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
|
||||
return this.stream().map(ApplicationModule::toString).collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
private ApplicationModules withSharedModules(Set<ApplicationModule> sharedModules) {
|
||||
return new ApplicationModules(metadata, modules, allClasses, rootPackages, sharedModules, orderedNames, verified);
|
||||
}
|
||||
|
||||
private FailureReport assertNoCyclesFor(JavaPackage rootPackage) {
|
||||
|
||||
EvaluationResult result = SlicesRuleDefinition.slices() //
|
||||
var result = SlicesRuleDefinition.slices() //
|
||||
.matching(rootPackage.getName().concat(".(*)..")) //
|
||||
.should().beFreeOfCycles() //
|
||||
.evaluate(allClasses.that(resideInAPackage(rootPackage.getName().concat(".."))));
|
||||
@@ -457,7 +486,7 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
|
||||
*/
|
||||
private ApplicationModule getRequiredModule(String moduleName) {
|
||||
|
||||
ApplicationModule module = modules.get(moduleName);
|
||||
var module = modules.get(moduleName);
|
||||
|
||||
if (module == null) {
|
||||
throw new IllegalArgumentException(String.format("Module %s does not exist!", moduleName));
|
||||
@@ -466,6 +495,32 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
|
||||
return module;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ApplicationModules} instance for the given {@link CacheKey}.
|
||||
*
|
||||
* @param key must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
private static ApplicationModules of(CacheKey key) {
|
||||
|
||||
Assert.notNull(key, "Cache key must not be null!");
|
||||
|
||||
var metadata = key.getMetadata();
|
||||
|
||||
var basePackages = new HashSet<String>();
|
||||
basePackages.add(key.getBasePackage());
|
||||
basePackages.addAll(metadata.getAdditionalPackages());
|
||||
|
||||
var modules = new ApplicationModules(metadata, basePackages, key.getIgnored(),
|
||||
metadata.useFullyQualifiedModuleNames(), IMPORT_OPTION);
|
||||
|
||||
var sharedModules = metadata.getSharedModuleNames() //
|
||||
.map(modules::getRequiredModule) //
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
return modules.withSharedModules(sharedModules);
|
||||
}
|
||||
|
||||
public static class Filters {
|
||||
|
||||
public static DescribedPredicate<JavaClass> withoutModules(String... names) {
|
||||
@@ -489,11 +544,22 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
|
||||
ModulithMetadata getMetadata();
|
||||
}
|
||||
|
||||
@Value(staticConstructor = "of")
|
||||
private static final class TypeKey implements CacheKey {
|
||||
|
||||
Class<?> type;
|
||||
DescribedPredicate<JavaClass> ignored;
|
||||
private final Class<?> type;
|
||||
private final DescribedPredicate<JavaClass> ignored;
|
||||
|
||||
/**
|
||||
* Creates a new {@link TypeKey} for the given type and {@link DescribedPredicate} of ignored {@link JavaClass}es.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param ignored must not be {@literal null}.
|
||||
*/
|
||||
TypeKey(Class<?> type, DescribedPredicate<JavaClass> ignored) {
|
||||
|
||||
this.type = type;
|
||||
this.ignored = ignored;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -512,13 +578,79 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
|
||||
public ModulithMetadata getMetadata() {
|
||||
return ModulithMetadata.of(type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.modulith.model.ApplicationModules.CacheKey#getIgnored()
|
||||
*/
|
||||
@Override
|
||||
public DescribedPredicate<JavaClass> getIgnored() {
|
||||
return ignored;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(obj instanceof TypeKey other)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Objects.equals(this.type, other.type) //
|
||||
&& Objects.equals(this.ignored, other.ignored);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(type, ignored);
|
||||
}
|
||||
}
|
||||
|
||||
@Value(staticConstructor = "of")
|
||||
private static final class PackageKey implements CacheKey {
|
||||
|
||||
String basePackage;
|
||||
DescribedPredicate<JavaClass> ignored;
|
||||
private final String basePackage;
|
||||
private final DescribedPredicate<JavaClass> ignored;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PackageKey} for the given base package and {@link DescribedPredicate} of ignored
|
||||
* {@link JavaClass}es.
|
||||
*
|
||||
* @param basePackage must not be {@literal null}.
|
||||
* @param ignored must not be {@literal null}.
|
||||
*/
|
||||
PackageKey(String basePackage, DescribedPredicate<JavaClass> ignored) {
|
||||
|
||||
this.basePackage = basePackage;
|
||||
this.ignored = ignored;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.modulith.model.ApplicationModules.CacheKey#getBasePackage()
|
||||
*/
|
||||
@Override
|
||||
public String getBasePackage() {
|
||||
return basePackage;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.modulith.model.ApplicationModules.CacheKey#getIgnored()
|
||||
*/
|
||||
public DescribedPredicate<JavaClass> getIgnored() {
|
||||
return ignored;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
@@ -528,6 +660,34 @@ public class ApplicationModules implements Iterable<ApplicationModule> {
|
||||
public ModulithMetadata getMetadata() {
|
||||
return ModulithMetadata.of(basePackage);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(obj instanceof PackageKey that)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Objects.equals(this.basePackage, that.basePackage) //
|
||||
&& Objects.equals(this.ignored, that.ignored);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(basePackage, ignored);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,11 +17,6 @@ package org.springframework.modulith.model;
|
||||
|
||||
import static org.springframework.modulith.model.Types.JavaXTypes.*;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
@@ -51,12 +46,15 @@ import com.tngtech.archunit.thirdparty.com.google.common.base.Suppliers;
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
public abstract class ArchitecturallyEvidentType {
|
||||
|
||||
private static Map<Key, ArchitecturallyEvidentType> CACHE = new HashMap<>();
|
||||
|
||||
private final @Getter JavaClass type;
|
||||
private final JavaClass type;
|
||||
|
||||
protected ArchitecturallyEvidentType(JavaClass type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ArchitecturallyEvidentType} for the given {@link JavaType} and {@link Classes} of Spring
|
||||
@@ -68,7 +66,7 @@ public abstract class ArchitecturallyEvidentType {
|
||||
*/
|
||||
public static ArchitecturallyEvidentType of(JavaClass type, Classes beanTypes) {
|
||||
|
||||
return CACHE.computeIfAbsent(Key.of(type, beanTypes), it -> {
|
||||
return CACHE.computeIfAbsent(new Key(type, beanTypes), it -> {
|
||||
|
||||
List<ArchitecturallyEvidentType> delegates = new ArrayList<>();
|
||||
|
||||
@@ -86,6 +84,15 @@ public abstract class ArchitecturallyEvidentType {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link JavaClass} backing the {@link ArchitecturallyEvidentType}.
|
||||
*
|
||||
* @return the type wnn
|
||||
*/
|
||||
public JavaClass getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the abbreviated (i.e. every package fragment reduced to its first character) full name.
|
||||
*
|
||||
@@ -620,18 +627,20 @@ public abstract class ArchitecturallyEvidentType {
|
||||
}
|
||||
}
|
||||
|
||||
@Value(staticConstructor = "of")
|
||||
private static class Key {
|
||||
private static record Key(JavaClass type, Classes beanTypes) {}
|
||||
|
||||
JavaClass type;
|
||||
Classes beanTypes;
|
||||
}
|
||||
|
||||
@Value
|
||||
public final class ReferenceMethod {
|
||||
public static class ReferenceMethod {
|
||||
|
||||
private final JavaMethod method;
|
||||
|
||||
public ReferenceMethod(JavaMethod method) {
|
||||
this.method = method;
|
||||
}
|
||||
|
||||
public JavaMethod getMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
public boolean isAsync() {
|
||||
return method.isAnnotatedWith(SpringTypes.AT_ASYNC) || method.isMetaAnnotatedWith(SpringTypes.AT_ASYNC);
|
||||
}
|
||||
|
||||
@@ -15,15 +15,13 @@
|
||||
*/
|
||||
package org.springframework.modulith.model;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collector;
|
||||
@@ -46,8 +44,6 @@ import com.tngtech.archunit.core.domain.properties.HasName;
|
||||
/**
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@ToString
|
||||
@EqualsAndHashCode
|
||||
class Classes implements DescribedIterable<JavaClass> {
|
||||
|
||||
public static Classes NONE = Classes.of(Collections.emptyList());
|
||||
@@ -65,7 +61,7 @@ class Classes implements DescribedIterable<JavaClass> {
|
||||
|
||||
this.classes = classes.stream() //
|
||||
.sorted(Comparator.comparing(JavaClass::getName)) //
|
||||
.collect(Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList));
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -190,35 +186,73 @@ class Classes implements DescribedIterable<JavaClass> {
|
||||
return classes.iterator();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Classes [classes=" + classes + "]";
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(obj instanceof Classes that)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Objects.equals(classes, that.classes);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(classes);
|
||||
}
|
||||
|
||||
String format() {
|
||||
|
||||
return classes.stream() //
|
||||
.map(Classes::format) //
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
String format(String basePackage) {
|
||||
|
||||
return classes.stream() //
|
||||
.map(it -> Classes.format(it, basePackage)) //
|
||||
.collect(Collectors.joining("\n"));
|
||||
}
|
||||
|
||||
private static String format(JavaClass type) {
|
||||
return format(type, "");
|
||||
}
|
||||
|
||||
static String format(JavaClass type, String basePackage) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
Assert.notNull(basePackage, "Base package must not be null!");
|
||||
|
||||
String prefix = type.getModifiers().contains(JavaModifier.PUBLIC) ? "+" : "o";
|
||||
String name = StringUtils.hasText(basePackage) //
|
||||
var prefix = type.getModifiers().contains(JavaModifier.PUBLIC) ? "+" : "o";
|
||||
var name = StringUtils.hasText(basePackage) //
|
||||
? type.getName().replace(basePackage, "…") //
|
||||
: type.getName();
|
||||
|
||||
return String.format("%s %s", prefix, name);
|
||||
}
|
||||
|
||||
private static String format(JavaClass type) {
|
||||
return format(type, "");
|
||||
}
|
||||
|
||||
private static class SameClass extends DescribedPredicate<JavaClass> {
|
||||
|
||||
private final JavaClass reference;
|
||||
|
||||
@@ -15,10 +15,6 @@
|
||||
*/
|
||||
package org.springframework.modulith.model;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NonNull;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -26,6 +22,7 @@ import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.modulith.Modulith;
|
||||
import org.springframework.modulith.Modulithic;
|
||||
import org.springframework.modulith.model.Types.SpringTypes;
|
||||
@@ -37,13 +34,24 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
class DefaultModulithMetadata implements ModulithMetadata {
|
||||
|
||||
private static final Class<? extends Annotation> AT_SPRING_BOOT_APPLICATION = Types
|
||||
.loadIfPresent(SpringTypes.AT_SPRING_BOOT_APPLICATION);
|
||||
|
||||
private final @NonNull Object modulithSource;
|
||||
private final @NonNull Object source;
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultModulithMetadata} for the given source.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
*/
|
||||
private DefaultModulithMetadata(Object source) {
|
||||
|
||||
Assert.notNull(source, "Source must not be null!");
|
||||
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ModulithMetadata} representing the defaults of a class annotated but not customized with
|
||||
@@ -80,7 +88,16 @@ class DefaultModulithMetadata implements ModulithMetadata {
|
||||
*/
|
||||
@Override
|
||||
public Object getModulithSource() {
|
||||
return modulithSource;
|
||||
return getSource();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.modulith.model.ModulithMetadata#getModulithSource()
|
||||
*/
|
||||
@Override
|
||||
public Object getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
*/
|
||||
package org.springframework.modulith.model;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
@@ -30,15 +29,9 @@ import com.tngtech.archunit.core.domain.JavaModifier;
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@Value
|
||||
public class EventType {
|
||||
|
||||
private final JavaClass type;
|
||||
|
||||
/**
|
||||
* The sources that create that event. Includes static factory methods that return an instance of the event type
|
||||
* itself as well as constructor invocations, except ones from the factory methods.
|
||||
*/
|
||||
private final List<Source> sources;
|
||||
|
||||
/**
|
||||
@@ -66,7 +59,68 @@ public class EventType {
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link JavaClass} of the {@link EventType}.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public JavaClass getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* The sources that create that event. Includes static factory methods that return an instance of the event type
|
||||
* itself as well as constructor invocations, except ones from the factory methods.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public List<Source> getSources() {
|
||||
return sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether any sources exist at all.
|
||||
*
|
||||
* @see #getSources()
|
||||
*/
|
||||
public boolean hasSources() {
|
||||
return !this.sources.isEmpty();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "EventType [type=" + type + ", sources=" + sources + "]";
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(obj instanceof EventType thaType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Objects.equals(sources, thaType.sources) //
|
||||
&& Objects.equals(type, thaType.type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(sources, type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
*/
|
||||
package org.springframework.modulith.model;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -37,7 +34,6 @@ import com.tngtech.archunit.thirdparty.com.google.common.base.Suppliers;
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class FormatableType {
|
||||
|
||||
private static final Map<String, FormatableType> CACHE = new ConcurrentHashMap<>();
|
||||
@@ -45,6 +41,21 @@ public class FormatableType {
|
||||
private final String type;
|
||||
private final Supplier<String> abbreviatedName;
|
||||
|
||||
/**
|
||||
* Creates a new {@link FormatableType} for the given source {@link String} and lazily computed abbreviated name.
|
||||
*
|
||||
* @param type must not be {@literal null} or empty.
|
||||
* @param abbreviatedName must not be {@literal null}.
|
||||
*/
|
||||
private FormatableType(String type, Supplier<String> abbreviatedName) {
|
||||
|
||||
Assert.hasText(type, "Type string must not be null or empty!");
|
||||
Assert.notNull(abbreviatedName, "Computed abbreviated name must not be null!");
|
||||
|
||||
this.type = type;
|
||||
this.abbreviatedName = abbreviatedName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link FormatableType} for the given {@link JavaClass}.
|
||||
*
|
||||
|
||||
@@ -17,20 +17,17 @@ package org.springframework.modulith.model;
|
||||
|
||||
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.*;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.tngtech.archunit.base.DescribedIterable;
|
||||
import com.tngtech.archunit.base.DescribedPredicate;
|
||||
@@ -42,19 +39,27 @@ import com.tngtech.archunit.thirdparty.com.google.common.base.Suppliers;
|
||||
/**
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class JavaPackage implements DescribedIterable<JavaClass> {
|
||||
|
||||
private static final String PACKAGE_INFO_NAME = "package-info";
|
||||
|
||||
private final @Getter String name;
|
||||
private final String name;
|
||||
private final Classes classes;
|
||||
private final Classes packageClasses;
|
||||
private final Supplier<Set<JavaPackage>> directSubPackages;
|
||||
|
||||
/**
|
||||
* Creates a new {@link JavaPackage} for the given {@link Classes}, name and whether to include all sub-packages.
|
||||
*
|
||||
* @param classes must not be {@literal null}.
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @param includeSubPackages
|
||||
*/
|
||||
private JavaPackage(Classes classes, String name, boolean includeSubPackages) {
|
||||
|
||||
Assert.notNull(classes, "Classes must not be null!");
|
||||
Assert.hasText(name, "Name must not be null or empty!");
|
||||
|
||||
this.classes = classes;
|
||||
this.packageClasses = classes.that(resideInAPackage(includeSubPackages ? name.concat("..") : name));
|
||||
this.name = name;
|
||||
@@ -67,22 +72,61 @@ public class JavaPackage implements DescribedIterable<JavaClass> {
|
||||
.collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link JavaPackage} for the given classes and name.
|
||||
*
|
||||
* @param classes must not be {@literal null}.
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
public static JavaPackage of(Classes classes, String name) {
|
||||
return new JavaPackage(classes, name, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given type is the {@code package-info.java} one.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
*/
|
||||
public static boolean isPackageInfoType(JavaClass type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
return type.getSimpleName().equals(PACKAGE_INFO_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the package.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduces the {@link JavaPackage} to only its base package.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public JavaPackage toSingle() {
|
||||
return new JavaPackage(classes, name, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the local name of the package, i.e. the last segment of the qualified package name.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public String getLocalName() {
|
||||
return name.substring(name.lastIndexOf(".") + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all direct sub-packages of the current one.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public Collection<JavaPackage> getDirectSubPackages() {
|
||||
return directSubPackages.get();
|
||||
}
|
||||
@@ -91,32 +135,22 @@ public class JavaPackage implements DescribedIterable<JavaClass> {
|
||||
* Returns all classes residing in the current package and potentially in sub-packages if the current package was
|
||||
* created to include them.
|
||||
*
|
||||
* @return
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public Classes getClasses() {
|
||||
return packageClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the direct sub-package name of the given candidate.
|
||||
* Returns all sub-packages that carry the given annotation type.
|
||||
*
|
||||
* @param candidate
|
||||
* @return
|
||||
* @param annotation must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
private String extractDirectSubPackage(String candidate) {
|
||||
|
||||
if (candidate.length() <= name.length()) {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
int subSubPackageIndex = candidate.indexOf('.', name.length() + 1);
|
||||
int endIndex = subSubPackageIndex == -1 ? candidate.length() : subSubPackageIndex;
|
||||
|
||||
return candidate.substring(0, endIndex);
|
||||
}
|
||||
|
||||
public Stream<JavaPackage> getSubPackagesAnnotatedWith(Class<? extends Annotation> annotation) {
|
||||
|
||||
Assert.notNull(annotation, "Annotation must not be null!");
|
||||
|
||||
return packageClasses.that(JavaClass.Predicates.simpleName(PACKAGE_INFO_NAME) //
|
||||
.and(CanBeAnnotated.Predicates.annotatedWith(annotation))).stream() //
|
||||
.map(JavaClass::getPackageName) //
|
||||
@@ -124,22 +158,59 @@ public class JavaPackage implements DescribedIterable<JavaClass> {
|
||||
.map(it -> of(classes, it));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all {@link Classes} that match the given {@link DescribedPredicate}.
|
||||
*
|
||||
* @param predicate must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Classes that(DescribedPredicate<? super JavaClass> predicate) {
|
||||
|
||||
Assert.notNull(predicate, "Predicate must not be null!");
|
||||
|
||||
return packageClasses.that(predicate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return whether the {@link JavaPackage} contains the given type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
*/
|
||||
public boolean contains(JavaClass type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
return packageClasses.contains(type);
|
||||
}
|
||||
|
||||
public boolean contains(String className) {
|
||||
return packageClasses.contains(className);
|
||||
/**
|
||||
* Returns whether the {@link JavaPackage} contains the type with the given name.
|
||||
*
|
||||
* @param typeName must not be {@literal null} or empty.
|
||||
*/
|
||||
public boolean contains(String typeName) {
|
||||
|
||||
Assert.hasText(typeName, "Type name must not be null or empty!");
|
||||
|
||||
return packageClasses.contains(typeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Stream} of all {@link JavaClass}es contained in the {@link JavaPackage}.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public Stream<JavaClass> stream() {
|
||||
return packageClasses.stream();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the annotation of the given type declared on the package.
|
||||
*
|
||||
* @param <A> the annotation type.
|
||||
* @param annotationType the annotation type to be found.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public <A extends Annotation> Optional<A> getAnnotation(Class<A> annotationType) {
|
||||
|
||||
return packageClasses.that(JavaClass.Predicates.simpleName(PACKAGE_INFO_NAME) //
|
||||
@@ -180,4 +251,52 @@ public class JavaPackage implements DescribedIterable<JavaClass> {
|
||||
.append('\n') //
|
||||
.toString();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(obj instanceof JavaPackage that)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(classes, directSubPackages, name, packageClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the direct sub-package name of the given candidate.
|
||||
*
|
||||
* @param candidate
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
private String extractDirectSubPackage(String candidate) {
|
||||
|
||||
if (candidate.length() <= name.length()) {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
int subSubPackageIndex = candidate.indexOf('.', name.length() + 1);
|
||||
int endIndex = subSubPackageIndex == -1 ? candidate.length() : subSubPackageIndex;
|
||||
|
||||
return candidate.substring(0, endIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,11 +61,20 @@ public interface ModulithMetadata {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the source of the Moduliths setup. Either a type or a package.
|
||||
* Returns the source of the Spring Modulith setup. Either a type or a package.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
* @deprecated use {@link #getSource()} instead.
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
Object getModulithSource();
|
||||
|
||||
/**
|
||||
* Returns the source of the Spring Modulith setup. Either a type or a package.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
Object getModulithSource();
|
||||
Object getSource();
|
||||
|
||||
/**
|
||||
* Returns the names of the packages that are supposed to be considered modulith base packages, i.e. for which to
|
||||
|
||||
@@ -15,10 +15,6 @@
|
||||
*/
|
||||
package org.springframework.modulith.model;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
@@ -32,21 +28,40 @@ import com.tngtech.archunit.core.domain.JavaModifier;
|
||||
import com.tngtech.archunit.core.domain.properties.HasModifiers;
|
||||
|
||||
/**
|
||||
* A named interface into an {@link ApplicationModule}. This can either be a package, explicitly annotated with
|
||||
* {@link org.springframework.modulith.NamedInterface} or a set of types annotated with the same annotation. Other
|
||||
* {@link ApplicationModules} can define allowed dependencies to particular named interfaces via the
|
||||
* {@code $moduleName::$namedInterfaceName} syntax.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
* @see org.springframework.modulith.ApplicationModule#allowedDependencies()
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
|
||||
public abstract class NamedInterface implements Iterable<JavaClass> {
|
||||
|
||||
private static final String UNNAMED_NAME = "<<UNNAMED>>";
|
||||
private static final String PACKAGE_INFO_NAME = "package-info";
|
||||
|
||||
protected final @Getter String name;
|
||||
protected final String name;
|
||||
|
||||
static NamedInterface unnamed(JavaPackage javaPackage) {
|
||||
return new PackageBasedNamedInterface(UNNAMED_NAME, javaPackage);
|
||||
/**
|
||||
* Creates a new {@link NamedInterface} with the given name.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
*/
|
||||
protected NamedInterface(String name) {
|
||||
|
||||
Assert.hasText(name, "Name must not be null or empty!");
|
||||
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public static List<PackageBasedNamedInterface> of(JavaPackage javaPackage) {
|
||||
/**
|
||||
* Returns all {@link PackageBasedNamedInterface}s for the given {@link JavaPackage}.
|
||||
*
|
||||
* @param javaPackage must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public static List<NamedInterface> of(JavaPackage javaPackage) {
|
||||
|
||||
String[] name = javaPackage.getAnnotation(org.springframework.modulith.NamedInterface.class) //
|
||||
.map(it -> it.name()) //
|
||||
@@ -54,33 +69,81 @@ public abstract class NamedInterface implements Iterable<JavaClass> {
|
||||
String.format("Couldn't find NamedInterface annotation on package %s!", javaPackage)));
|
||||
|
||||
return Arrays.stream(name) //
|
||||
.map(it -> new PackageBasedNamedInterface(it, javaPackage)) //
|
||||
.<NamedInterface> map(it -> new PackageBasedNamedInterface(it, javaPackage)) //
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link TypeBasedNamedInterface} with the given name, {@link Classes} and base {@link JavaPackage}.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @param classes must not be {@literal null}.
|
||||
* @param basePackage must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public static TypeBasedNamedInterface of(String name, Classes classes, JavaPackage basePackage) {
|
||||
return new TypeBasedNamedInterface(name, classes, basePackage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an unnamed {@link NamedInterface} for the given {@link JavaPackage}.
|
||||
*
|
||||
* @param javaPackage must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
static NamedInterface unnamed(JavaPackage javaPackage) {
|
||||
return new PackageBasedNamedInterface(UNNAMED_NAME, javaPackage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link NamedInterface}'s name.
|
||||
*
|
||||
* @return will never be {@literal null} or empty.
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this is the unnamed (implicit) {@link NamedInterface}.
|
||||
*/
|
||||
public boolean isUnnamed() {
|
||||
return name.equals(UNNAMED_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the {@link NamedInterface} contains the given {@link JavaClass}.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
*/
|
||||
public boolean contains(JavaClass type) {
|
||||
|
||||
Assert.notNull(type, "JavaClass must not be null!");
|
||||
|
||||
return getClasses().contains(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the {@link NamedInterface} contains the given type.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
*/
|
||||
public boolean contains(Class<?> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
return !getClasses().that(Predicates.equivalentTo(type)).isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given {@link NamedInterface} has the same name as the current one.
|
||||
*
|
||||
* @param other
|
||||
* @return
|
||||
* @param other must not be {@literal null}.
|
||||
*/
|
||||
boolean hasSameNameAs(NamedInterface other) {
|
||||
|
||||
Assert.notNull(other, "NamedInterface must not be null!");
|
||||
|
||||
return this.name.equals(other.name);
|
||||
}
|
||||
|
||||
@@ -93,13 +156,24 @@ public abstract class NamedInterface implements Iterable<JavaClass> {
|
||||
return getClasses().iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all {@link Classes} making up this {@link NamedInterface}.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
protected abstract Classes getClasses();
|
||||
|
||||
/**
|
||||
* Merges the current {@link NamedInterface} with the given {@link TypeBasedNamedInterface}.
|
||||
*
|
||||
* @param other must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public abstract NamedInterface merge(TypeBasedNamedInterface other);
|
||||
|
||||
static class PackageBasedNamedInterface extends NamedInterface {
|
||||
private static class PackageBasedNamedInterface extends NamedInterface {
|
||||
|
||||
private final @Getter Classes classes;
|
||||
private final Classes classes;
|
||||
private final JavaPackage javaPackage;
|
||||
|
||||
public PackageBasedNamedInterface(String name, JavaPackage pkg) {
|
||||
@@ -123,6 +197,15 @@ public abstract class NamedInterface implements Iterable<JavaClass> {
|
||||
this.javaPackage = pkg;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.modulith.model.NamedInterface#getClasses()
|
||||
*/
|
||||
@Override
|
||||
public Classes getClasses() {
|
||||
return classes;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.modulith.model.NamedInterface#merge(org.springframework.modulith.model.NamedInterface.TypeBasedNamedInterface)
|
||||
@@ -143,18 +226,38 @@ public abstract class NamedInterface implements Iterable<JavaClass> {
|
||||
}
|
||||
}
|
||||
|
||||
static class TypeBasedNamedInterface extends NamedInterface {
|
||||
public static class TypeBasedNamedInterface extends NamedInterface {
|
||||
|
||||
private final @Getter Classes classes;
|
||||
private final Classes classes;
|
||||
private final JavaPackage pkg;
|
||||
|
||||
/**
|
||||
* Creates a new {@link TypeBasedNamedInterface} with the given name, {@link Classes} and {@link JavaPackage}.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @param types must not be {@literal null}.
|
||||
* @param pkg must not be {@literal null}.
|
||||
*/
|
||||
public TypeBasedNamedInterface(String name, Classes types, JavaPackage pkg) {
|
||||
|
||||
super(name);
|
||||
|
||||
Assert.notNull(types, "Classes must not be null!");
|
||||
Assert.notNull(pkg, "JavaPackage must not be null!");
|
||||
|
||||
this.classes = types;
|
||||
this.pkg = pkg;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.modulith.model.NamedInterface#getClasses()
|
||||
*/
|
||||
@Override
|
||||
public Classes getClasses() {
|
||||
return classes;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.modulith.model.NamedInterface#merge(org.springframework.modulith.model.NamedInterface.TypeBasedNamedInterface)
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
*/
|
||||
package org.springframework.modulith.model;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
@@ -28,112 +25,101 @@ import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.core.annotation.AnnotatedElementUtils;
|
||||
import org.springframework.modulith.model.NamedInterface.TypeBasedNamedInterface;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import com.tngtech.archunit.core.domain.JavaClass;
|
||||
|
||||
/**
|
||||
* A collection of {@link NamedInterface}s.
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class NamedInterfaces implements Iterable<NamedInterface> {
|
||||
|
||||
public static final NamedInterfaces NONE = new NamedInterfaces(Collections.emptyList());
|
||||
|
||||
private final List<NamedInterface> namedInterfaces;
|
||||
|
||||
public static NamedInterfaces discoverNamedInterfaces(JavaPackage basePackage) {
|
||||
/**
|
||||
* Creates a new {@link NamedInterfaces} for all {@link NamedInterface}s.
|
||||
*
|
||||
* @param namedInterfaces must not be {@literal null}.
|
||||
*/
|
||||
private NamedInterfaces(List<NamedInterface> namedInterfaces) {
|
||||
|
||||
Assert.notNull(namedInterfaces, "Named interfaces must not be null!");
|
||||
|
||||
this.namedInterfaces = namedInterfaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discovers all {@link NamedInterfaces} declared for the given {@link JavaPackage}.
|
||||
*
|
||||
* @param basePackage must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
static NamedInterfaces discoverNamedInterfaces(JavaPackage basePackage) {
|
||||
|
||||
return NamedInterfaces.ofAnnotatedPackages(basePackage) //
|
||||
.and(NamedInterfaces.ofAnnotatedTypes(basePackage)) //
|
||||
.and(NamedInterface.unnamed(basePackage));
|
||||
}
|
||||
|
||||
public static NamedInterfaces of(List<NamedInterface> interfaces) {
|
||||
/**
|
||||
* Creates a new {@link NamedInterfaces} for the given {@link NamedInterface}s.
|
||||
*
|
||||
* @param interfaces must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
static NamedInterfaces of(List<NamedInterface> interfaces) {
|
||||
return interfaces.isEmpty() ? NONE : new NamedInterfaces(interfaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link NamedInterfaces} for the given base {@link JavaPackage}.
|
||||
*
|
||||
* @param basePackage must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
static NamedInterfaces ofAnnotatedPackages(JavaPackage basePackage) {
|
||||
|
||||
Assert.notNull(basePackage, "Base package must not be null!");
|
||||
|
||||
return basePackage //
|
||||
.getSubPackagesAnnotatedWith(org.springframework.modulith.NamedInterface.class) //
|
||||
.flatMap(it -> NamedInterface.of(it).stream()) //
|
||||
.collect(Collectors.collectingAndThen(Collectors.toList(), NamedInterfaces::of));
|
||||
}
|
||||
|
||||
private static List<TypeBasedNamedInterface> ofAnnotatedTypes(JavaPackage basePackage) {
|
||||
|
||||
MultiValueMap<String, JavaClass> mappings = new LinkedMultiValueMap<>();
|
||||
|
||||
basePackage.stream() //
|
||||
.filter(it -> !JavaPackage.isPackageInfoType(it)) //
|
||||
.forEach(it -> {
|
||||
|
||||
if (!it.isAnnotatedWith(org.springframework.modulith.NamedInterface.class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
org.springframework.modulith.NamedInterface annotation = AnnotatedElementUtils
|
||||
.getMergedAnnotation(it.reflect(), org.springframework.modulith.NamedInterface.class);
|
||||
|
||||
for (String name : annotation.name()) {
|
||||
mappings.add(name, it);
|
||||
}
|
||||
});
|
||||
|
||||
return mappings.entrySet().stream() //
|
||||
.map(entry -> NamedInterface.of(entry.getKey(), Classes.of(entry.getValue()), basePackage)) //
|
||||
.toList();
|
||||
}
|
||||
|
||||
private NamedInterfaces and(NamedInterface namedInterface) {
|
||||
|
||||
List<NamedInterface> result = new ArrayList<>(namedInterfaces.size() + 1);
|
||||
result.addAll(namedInterfaces);
|
||||
result.add(namedInterface);
|
||||
|
||||
return new NamedInterfaces(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether at least one explicit {@link NamedInterface} is declared.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public boolean hasExplicitInterfaces() {
|
||||
return namedInterfaces.size() > 1 || !namedInterfaces.get(0).isUnnamed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link Stream} of {@link NamedInterface}s.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public Stream<NamedInterface> stream() {
|
||||
return namedInterfaces.stream();
|
||||
}
|
||||
|
||||
public NamedInterfaces and(List<TypeBasedNamedInterface> others) {
|
||||
|
||||
List<NamedInterface> namedInterfaces = new ArrayList<>();
|
||||
List<NamedInterface> unmergedInterface = this.namedInterfaces;
|
||||
|
||||
for (TypeBasedNamedInterface candidate : others) {
|
||||
|
||||
Optional<NamedInterface> existing = namedInterfaces.stream() //
|
||||
.filter(it -> it.hasSameNameAs(candidate)) //
|
||||
.findFirst();
|
||||
|
||||
// Merge existing with new and add to result
|
||||
existing.ifPresent(it -> {
|
||||
namedInterfaces.add(it.merge(candidate));
|
||||
namedInterfaces.add(it);
|
||||
unmergedInterface.remove(it);
|
||||
});
|
||||
|
||||
// Simply add candidate
|
||||
if (!existing.isPresent()) {
|
||||
namedInterfaces.add(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
namedInterfaces.addAll(unmergedInterface);
|
||||
|
||||
return new NamedInterfaces(namedInterfaces);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link NamedInterface} with the given name if present.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public Optional<NamedInterface> getByName(String name) {
|
||||
|
||||
Assert.hasText(name, "Named interface name must not be null or empty!");
|
||||
|
||||
return namedInterfaces.stream().filter(it -> it.getName().equals(name)).findFirst();
|
||||
}
|
||||
|
||||
@@ -158,4 +144,79 @@ public class NamedInterfaces implements Iterable<NamedInterface> {
|
||||
public Iterator<NamedInterface> iterator() {
|
||||
return namedInterfaces.iterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link NamedInterfaces} instance with the given {@link TypeBasedNamedInterface}s added.
|
||||
*
|
||||
* @param others must not be {@literal null}.
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
NamedInterfaces and(List<TypeBasedNamedInterface> others) {
|
||||
|
||||
Assert.notNull(others, "Other TypeBasedNamedInterfaces must not be null!");
|
||||
|
||||
var namedInterfaces = new ArrayList<NamedInterface>();
|
||||
var unmergedInterface = this.namedInterfaces;
|
||||
|
||||
if (others.isEmpty()) {
|
||||
return this;
|
||||
}
|
||||
|
||||
for (TypeBasedNamedInterface candidate : others) {
|
||||
|
||||
var existing = namedInterfaces.stream() //
|
||||
.filter(it -> it.hasSameNameAs(candidate)) //
|
||||
.findFirst();
|
||||
|
||||
// Merge existing with new and add to result
|
||||
existing.ifPresent(it -> {
|
||||
namedInterfaces.add(it.merge(candidate));
|
||||
namedInterfaces.add(it);
|
||||
unmergedInterface.remove(it);
|
||||
});
|
||||
|
||||
// Simply add candidate
|
||||
if (!existing.isPresent()) {
|
||||
namedInterfaces.add(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
namedInterfaces.addAll(unmergedInterface);
|
||||
|
||||
return new NamedInterfaces(namedInterfaces);
|
||||
}
|
||||
|
||||
private NamedInterfaces and(NamedInterface namedInterface) {
|
||||
|
||||
var result = new ArrayList<NamedInterface>(namedInterfaces.size() + 1);
|
||||
result.addAll(namedInterfaces);
|
||||
result.add(namedInterface);
|
||||
|
||||
return new NamedInterfaces(result);
|
||||
}
|
||||
|
||||
private static List<TypeBasedNamedInterface> ofAnnotatedTypes(JavaPackage basePackage) {
|
||||
|
||||
var mappings = new LinkedMultiValueMap<String, JavaClass>();
|
||||
|
||||
basePackage.stream() //
|
||||
.filter(it -> !JavaPackage.isPackageInfoType(it)) //
|
||||
.forEach(it -> {
|
||||
|
||||
if (!it.isAnnotatedWith(org.springframework.modulith.NamedInterface.class)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var annotation = AnnotatedElementUtils.getMergedAnnotation(it.reflect(),
|
||||
org.springframework.modulith.NamedInterface.class);
|
||||
|
||||
for (String name : annotation.name()) {
|
||||
mappings.add(name, it);
|
||||
}
|
||||
});
|
||||
|
||||
return mappings.entrySet().stream() //
|
||||
.map(entry -> NamedInterface.of(entry.getKey(), Classes.of(entry.getValue()), basePackage)) //
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,10 @@
|
||||
*/
|
||||
package org.springframework.modulith.model;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.tngtech.archunit.core.domain.JavaClass;
|
||||
|
||||
@@ -29,17 +27,49 @@ import com.tngtech.archunit.core.domain.JavaClass;
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@EqualsAndHashCode
|
||||
@RequiredArgsConstructor(staticName = "of", access = AccessLevel.PACKAGE)
|
||||
public class SpringBean {
|
||||
|
||||
private final @Getter JavaClass type;
|
||||
private final JavaClass type;
|
||||
private final ApplicationModule module;
|
||||
|
||||
/**
|
||||
* Creates a new {@link SpringBean} for the given {@link JavaClass} and {@link ApplicationModule}.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param module must not be {@literal null}.
|
||||
*/
|
||||
private SpringBean(JavaClass type, ApplicationModule module) {
|
||||
|
||||
Assert.notNull(type, "JavaClass must not be null!");
|
||||
Assert.notNull(module, "ApplicationModule must not be null!");
|
||||
|
||||
this.type = type;
|
||||
this.module = module;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link SpringBean} for the given {@link JavaClass} and {@link ApplicationModule}.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param module must not be {@literal null}.
|
||||
*/
|
||||
static SpringBean of(JavaClass type, ApplicationModule module) {
|
||||
return new SpringBean(type, module);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link JavaClass} of the {@link SpringBean}.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public JavaClass getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the fully-qualified name of the Spring bean type.
|
||||
*
|
||||
* @return
|
||||
* @return will never be {@literal null} or empty.
|
||||
*/
|
||||
public String getFullyQualifiedTypeName() {
|
||||
return type.getFullName();
|
||||
@@ -56,9 +86,9 @@ public class SpringBean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all interfaces implemented by the bean that are part of the same module.
|
||||
* Returns all interfaces implemented by the bean that are part of the same application module.
|
||||
*
|
||||
* @return
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
public List<JavaClass> getInterfacesWithinModule() {
|
||||
|
||||
@@ -67,11 +97,40 @@ public class SpringBean {
|
||||
.toList();
|
||||
}
|
||||
|
||||
public boolean isAnnotatedWith(Class<?> type) {
|
||||
return Types.isAnnotatedWith(type).test(this.type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link ArchitecturallyEvidentType} from the current {@link SpringBean}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ArchitecturallyEvidentType toArchitecturallyEvidentType() {
|
||||
return ArchitecturallyEvidentType.of(type, module.getSpringBeansInternal());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!(obj instanceof SpringBean that)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Objects.equals(this.module, that.module) //
|
||||
&& Objects.equals(this.type, that.type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(type, module);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,6 @@ package org.springframework.modulith.model;
|
||||
|
||||
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.*;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
@@ -33,12 +31,11 @@ import com.tngtech.archunit.core.domain.properties.CanBeAnnotated.Predicates;
|
||||
/**
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@UtilityClass
|
||||
class Types {
|
||||
|
||||
@Nullable
|
||||
@SuppressWarnings("unchecked")
|
||||
<T> Class<T> loadIfPresent(String name) {
|
||||
static <T> Class<T> loadIfPresent(String name) {
|
||||
|
||||
ClassLoader loader = Types.class.getClassLoader();
|
||||
|
||||
@@ -79,7 +76,6 @@ class Types {
|
||||
}
|
||||
}
|
||||
|
||||
@UtilityClass
|
||||
static class JavaXTypes {
|
||||
|
||||
private static final String BASE_PACKAGE = "jakarta";
|
||||
@@ -93,7 +89,6 @@ class Types {
|
||||
}
|
||||
}
|
||||
|
||||
@UtilityClass
|
||||
static class SpringTypes {
|
||||
|
||||
private static final String BASE_PACKAGE = "org.springframework";
|
||||
@@ -129,7 +124,6 @@ class Types {
|
||||
}
|
||||
}
|
||||
|
||||
@UtilityClass
|
||||
static class SpringDataTypes {
|
||||
|
||||
private static final String BASE_PACKAGE = SpringTypes.BASE_PACKAGE + ".data";
|
||||
@@ -147,11 +141,11 @@ class Types {
|
||||
}
|
||||
}
|
||||
|
||||
DescribedPredicate<CanBeAnnotated> isAnnotatedWith(Class<?> type) {
|
||||
static DescribedPredicate<CanBeAnnotated> isAnnotatedWith(Class<?> type) {
|
||||
return isAnnotatedWith(type.getName());
|
||||
}
|
||||
|
||||
DescribedPredicate<CanBeAnnotated> isAnnotatedWith(String type) {
|
||||
static DescribedPredicate<CanBeAnnotated> isAnnotatedWith(String type) {
|
||||
return Predicates.annotatedWith(type) //
|
||||
.or(Predicates.metaAnnotatedWith(type));
|
||||
}
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
*/
|
||||
package org.springframework.modulith.model;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@@ -32,7 +29,6 @@ import org.springframework.util.Assert;
|
||||
*
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
@RequiredArgsConstructor(staticName = "of", access = AccessLevel.PRIVATE)
|
||||
public class Violations extends RuntimeException {
|
||||
|
||||
private static final long serialVersionUID = 6863781504675034691L;
|
||||
@@ -41,13 +37,25 @@ public class Violations extends RuntimeException {
|
||||
|
||||
private final List<RuntimeException> exceptions;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Violations} from the given {@link RuntimeException}s.
|
||||
*
|
||||
* @param exceptions must not be {@literal null}.
|
||||
*/
|
||||
private Violations(List<RuntimeException> exceptions) {
|
||||
|
||||
Assert.notNull(exceptions, "Exceptions must not be null!");
|
||||
|
||||
this.exceptions = exceptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link Collector} to turn a {@link Stream} of {@link RuntimeException}s into a {@link Violations} instance.
|
||||
*
|
||||
* @return will never be {@literal null}.
|
||||
*/
|
||||
static Collector<RuntimeException, ?, Violations> toViolations() {
|
||||
return Collectors.collectingAndThen(Collectors.toList(), Violations::of);
|
||||
return Collectors.collectingAndThen(Collectors.toList(), Violations::new);
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
@@ -19,7 +19,6 @@ import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import com.tngtech.archunit.core.domain.JavaClasses;
|
||||
import com.tngtech.archunit.core.importer.ClassFileImporter;
|
||||
import com.tngtech.archunit.core.importer.ImportOption;
|
||||
|
||||
@@ -47,11 +46,11 @@ class ModuleDetectionStrategyUnitTest {
|
||||
@Test
|
||||
void detectsJMoleculesAnnotatedModule() {
|
||||
|
||||
JavaClasses classes = new ClassFileImporter() //
|
||||
var classes = new ClassFileImporter() //
|
||||
.withImportOption(new ImportOption.OnlyIncludeTests()) //
|
||||
.importPackages("jmolecules");
|
||||
|
||||
JavaPackage javaPackage = JavaPackage.of(Classes.of(classes), "jmolecules");
|
||||
var javaPackage = JavaPackage.of(Classes.of(classes), "jmolecules");
|
||||
|
||||
assertThat(ApplicationModuleDetectionStrategy.explictlyAnnotated().getModuleBasePackages(javaPackage))
|
||||
.containsExactly(javaPackage);
|
||||
|
||||
Reference in New Issue
Block a user