Skip to content

Commit

Permalink
Add JavaClass type parameters to dependencies #484
Browse files Browse the repository at this point in the history
This is the next step of #398. It will make the imported type parameters of `JavaClass` widely useful by adding type parameter dependencies (e.g. `Bar` for `class Foo<T extends Bar>`) to the `JavaClass.directDependencies{From/To}Self`.
This way type parameter dependencies will now cause violations in all dependency based `ArchRules` like `LayeredArchitecture` or any `classes()...dependOn...()` fluent API methods.
  • Loading branch information
codecholeric authored Dec 15, 2020
2 parents 99841dc + 301ccd8 commit 44698c9
Show file tree
Hide file tree
Showing 22 changed files with 361 additions and 123 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
package com.tngtech.archunit.example.layers.service;

import java.util.Map;
import java.util.Set;

import com.tngtech.archunit.example.layers.controller.SomeUtility;
import com.tngtech.archunit.example.layers.controller.one.SomeEnum;
import com.tngtech.archunit.example.layers.security.Secured;

/**
* Well modelled code always has lots of 'helpers' ;-)
*/
public class ServiceHelper {
@SuppressWarnings("unused")
public class ServiceHelper<
TYPE_PARAMETER_VIOLATING_LAYER_RULE extends SomeUtility,
ANOTHER_TYPE_PARAMETER_VIOLATING_LAYER_RULE extends Map<?, Set<? super SomeEnum>>> {

public Object insecure = new Object();
@Secured
public Object properlySecured = new Object();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@
import static com.tngtech.archunit.testutils.ExpectedDependency.field;
import static com.tngtech.archunit.testutils.ExpectedDependency.inheritanceFrom;
import static com.tngtech.archunit.testutils.ExpectedDependency.method;
import static com.tngtech.archunit.testutils.ExpectedDependency.typeParameter;
import static com.tngtech.archunit.testutils.ExpectedLocation.javaClass;
import static com.tngtech.archunit.testutils.ExpectedNaming.simpleNameOf;
import static com.tngtech.archunit.testutils.ExpectedNaming.simpleNameOfAnonymousClassOf;
Expand Down Expand Up @@ -739,6 +740,8 @@ Stream<DynamicTest> LayerDependencyRulesTest() {
.by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController)
.toMethod(UseCaseTwoController.class, doSomethingTwo)
.inLine(25).asDependency())
.by(typeParameter(ServiceHelper.class, "TYPE_PARAMETER_VIOLATING_LAYER_RULE").dependingOn(SomeUtility.class))
.by(typeParameter(ServiceHelper.class, "ANOTHER_TYPE_PARAMETER_VIOLATING_LAYER_RULE").dependingOn(SomeEnum.class))
.by(method(ServiceViolatingLayerRules.class, dependentMethod).withParameter(UseCaseTwoController.class))
.by(method(ServiceViolatingLayerRules.class, dependentMethod).withReturnType(SomeGuiController.class))
.by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).withParameter(UseCaseTwoController[].class))
Expand Down Expand Up @@ -796,6 +799,8 @@ Stream<DynamicTest> LayerDependencyRulesTest() {
.by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController)
.toMethod(UseCaseTwoController.class, doSomethingTwo)
.inLine(25).asDependency())
.by(typeParameter(ServiceHelper.class, "TYPE_PARAMETER_VIOLATING_LAYER_RULE").dependingOn(SomeUtility.class))
.by(typeParameter(ServiceHelper.class, "ANOTHER_TYPE_PARAMETER_VIOLATING_LAYER_RULE").dependingOn(SomeEnum.class))
.by(method(ServiceViolatingLayerRules.class, dependentMethod).withParameter(UseCaseTwoController.class))
.by(method(ServiceViolatingLayerRules.class, dependentMethod).withReturnType(SomeGuiController.class))
.by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).withParameter(UseCaseTwoController[].class))
Expand Down Expand Up @@ -863,6 +868,8 @@ Stream<DynamicTest> LayeredArchitectureTest() {
.inLine(27)
.asDependency())

.by(typeParameter(ServiceHelper.class, "TYPE_PARAMETER_VIOLATING_LAYER_RULE").dependingOn(SomeUtility.class))
.by(typeParameter(ServiceHelper.class, "ANOTHER_TYPE_PARAMETER_VIOLATING_LAYER_RULE").dependingOn(SomeEnum.class))
.by(method(ServiceViolatingLayerRules.class, dependentMethod).withParameter(UseCaseTwoController.class))
.by(method(ServiceViolatingLayerRules.class, dependentMethod).withReturnType(SomeGuiController.class))
.by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ public static InheritanceCreator inheritanceFrom(Class<?> clazz) {
return new InheritanceCreator(clazz);
}

public static TypeParameterCreator typeParameter(Class<?> clazz, String typeParameterName) {
return new TypeParameterCreator(clazz, typeParameterName);
}

public static AnnotationDependencyCreator annotatedClass(Class<?> clazz) {
return new AnnotationDependencyCreator(clazz);
}
Expand Down Expand Up @@ -85,6 +89,21 @@ public ExpectedDependency implementing(Class<?> anInterface) {
}
}

public static class TypeParameterCreator {
private final Class<?> clazz;
private final String typeParameterName;

private TypeParameterCreator(Class<?> clazz, String typeParameterName) {
this.clazz = clazz;
this.typeParameterName = typeParameterName;
}

public ExpectedDependency dependingOn(Class<?> typeParameterDependency) {
return new ExpectedDependency(clazz, typeParameterDependency,
getDependencyPattern(clazz.getName(), "has type parameter '" + typeParameterName + "' depending on", typeParameterDependency.getName(), 0));
}
}

public static class AccessCreator {
private final Class<?> originClass;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,26 +120,31 @@ static Set<Dependency> tryCreateFromInstanceofCheck(InstanceofCheck instanceofCh
}

static Set<Dependency> tryCreateFromAnnotation(JavaAnnotation<?> target) {
Origin origin = findSuitableOrigin(target);
Origin origin = findSuitableOrigin(target, target.getAnnotatedElement());
return tryCreateDependency(origin.originClass, origin.originDescription, "is annotated with", target.getRawType());
}

static Set<Dependency> tryCreateFromAnnotationMember(JavaAnnotation<?> annotation, JavaClass memberType) {
Origin origin = findSuitableOrigin(annotation);
Origin origin = findSuitableOrigin(annotation, annotation.getAnnotatedElement());
return tryCreateDependency(origin.originClass, origin.originDescription, "has annotation member of type", memberType);
}

private static Origin findSuitableOrigin(JavaAnnotation<?> annotation) {
Object annotatedElement = annotation.getAnnotatedElement();
if (annotatedElement instanceof JavaMember) {
JavaMember member = (JavaMember) annotatedElement;
static Set<Dependency> tryCreateFromTypeParameter(JavaTypeVariable<?> typeParameter, JavaClass typeParameterDependency) {
String dependencyType = "has type parameter '" + typeParameter.getName() + "' depending on";
Origin origin = findSuitableOrigin(typeParameter, typeParameter.getOwner());
return tryCreateDependency(origin.originClass, origin.originDescription, dependencyType, typeParameterDependency);
}

private static Origin findSuitableOrigin(Object dependencyCause, Object originCandidate) {
if (originCandidate instanceof JavaMember) {
JavaMember member = (JavaMember) originCandidate;
return new Origin(member.getOwner(), member.getDescription());
}
if (annotatedElement instanceof JavaClass) {
JavaClass clazz = (JavaClass) annotatedElement;
if (originCandidate instanceof JavaClass) {
JavaClass clazz = (JavaClass) originCandidate;
return new Origin(clazz, clazz.getDescription());
}
throw new IllegalStateException("Could not find suitable dependency origin for " + annotation);
throw new IllegalStateException("Could not find suitable dependency origin for " + dependencyCause);
}

private static Set<Dependency> tryCreateDependencyFromJavaMember(JavaMember origin, String dependencyType, JavaClass target) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,11 @@ public static InstanceofCheck createInstanceofCheck(JavaCodeUnit codeUnit, JavaC
return InstanceofCheck.from(codeUnit, target, lineNumber);
}

public static JavaTypeVariable createTypeVariable(String name, JavaClass erasure) {
return new JavaTypeVariable(name, erasure);
public static <OWNER extends HasDescription> JavaTypeVariable<OWNER> createTypeVariable(String name, OWNER owner, JavaClass erasure) {
return new JavaTypeVariable<>(name, owner, erasure);
}

public static void completeTypeVariable(JavaTypeVariable variable, List<JavaType> upperBounds) {
public static void completeTypeVariable(JavaTypeVariable<?> variable, List<JavaType> upperBounds) {
variable.setUpperBounds(upperBounds);
}

Expand All @@ -164,7 +164,7 @@ public static JavaGenericArrayType createGenericArrayType(JavaType componentType
return new JavaGenericArrayType(componentType.getName() + "[]", componentType, erasure);
}

public static JavaWildcardType createWildcardType(JavaWildcardTypeBuilder builder) {
public static JavaWildcardType createWildcardType(JavaWildcardTypeBuilder<?> builder) {
return new JavaWildcardType(builder);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public interface ImportContext {

Set<JavaClass> createInterfaces(JavaClass owner);

List<JavaTypeVariable> createTypeParameters(JavaClass owner);
List<JavaTypeVariable<JavaClass>> createTypeParameters(JavaClass owner);

Set<JavaField> createFields(JavaClass owner);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public class JavaClass implements JavaType, HasName.AndFullName, HasAnnotations<
private final boolean isAnonymousClass;
private final boolean isMemberClass;
private final Set<JavaModifier> modifiers;
private List<JavaTypeVariable> typeParameters = emptyList();
private List<JavaTypeVariable<JavaClass>> typeParameters = emptyList();
private final Supplier<Class<?>> reflectSupplier;
private Set<JavaField> fields = emptySet();
private Set<JavaCodeUnit> codeUnits = emptySet();
Expand Down Expand Up @@ -644,7 +644,7 @@ public Optional<JavaAnnotation<JavaClass>> tryGetAnnotationOfType(String typeNam
}

@PublicAPI(usage = ACCESS)
public List<JavaTypeVariable> getTypeParameters() {
public List<JavaTypeVariable<JavaClass>> getTypeParameters() {
return typeParameters;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.tngtech.archunit.core.domain.properties.HasAnnotations;

import static com.google.common.base.Suppliers.memoize;
import static com.google.common.collect.Iterables.concat;

class JavaClassDependencies {
private final JavaClass javaClass;
Expand All @@ -49,6 +50,7 @@ public Set<Dependency> get() {
result.addAll(constructorParameterDependenciesFromSelf());
result.addAll(annotationDependenciesFromSelf());
result.addAll(instanceofCheckDependenciesFromSelf());
result.addAll(typeParameterDependenciesFromSelf());
return result.build();
}
});
Expand Down Expand Up @@ -139,6 +141,53 @@ private Set<Dependency> instanceofCheckDependenciesFromSelf() {
return result.build();
}

private Set<Dependency> typeParameterDependenciesFromSelf() {
ImmutableSet.Builder<Dependency> result = ImmutableSet.builder();
for (JavaTypeVariable<?> typeVariable : javaClass.getTypeParameters()) {
result.addAll(getDependenciesFromTypeParameter(typeVariable));
}
return result.build();
}

private Set<Dependency> getDependenciesFromTypeParameter(JavaTypeVariable<?> typeVariable) {
ImmutableSet.Builder<Dependency> dependenciesBuilder = ImmutableSet.builder();
for (JavaType bound : typeVariable.getUpperBounds()) {
for (JavaClass typeParameterDependency : dependenciesOfType(bound)) {
dependenciesBuilder.addAll(Dependency.tryCreateFromTypeParameter(typeVariable, typeParameterDependency));
}
}
return dependenciesBuilder.build();
}

private static Iterable<JavaClass> dependenciesOfType(JavaType javaType) {
ImmutableSet.Builder<JavaClass> result = ImmutableSet.builder();
if (javaType instanceof JavaClass) {
result.add((JavaClass) javaType);
} else if (javaType instanceof JavaParameterizedType) {
result.addAll(dependenciesOfParameterizedType((JavaParameterizedType) javaType));
} else if (javaType instanceof JavaWildcardType) {
result.addAll(dependenciesOfWildcardType((JavaWildcardType) javaType));
}
return result.build();
}

private static Set<JavaClass> dependenciesOfParameterizedType(JavaParameterizedType parameterizedType) {
ImmutableSet.Builder<JavaClass> result = ImmutableSet.<JavaClass>builder()
.add(parameterizedType.toErasure());
for (JavaType typeArgument : parameterizedType.getActualTypeArguments()) {
result.addAll(dependenciesOfType(typeArgument));
}
return result.build();
}

private static Set<JavaClass> dependenciesOfWildcardType(JavaWildcardType javaType) {
ImmutableSet.Builder<JavaClass> result = ImmutableSet.builder();
for (JavaType bound : concat(javaType.getUpperBounds(), javaType.getLowerBounds())) {
result.addAll(dependenciesOfType(bound));
}
return result.build();
}

private <T extends HasDescription & HasAnnotations<?>> Set<Dependency> annotationDependencies(Set<T> annotatedObjects) {
ImmutableSet.Builder<Dependency> result = ImmutableSet.builder();
for (T annotated : annotatedObjects) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@
*/
package com.tngtech.archunit.core.domain;

import java.lang.reflect.TypeVariable;
import java.util.List;

import com.google.common.base.Joiner;
import com.google.common.collect.FluentIterable;
import com.tngtech.archunit.PublicAPI;
import com.tngtech.archunit.base.HasDescription;
import com.tngtech.archunit.core.domain.properties.HasOwner;
import com.tngtech.archunit.core.domain.properties.HasUpperBounds;

import static com.google.common.collect.Iterables.getOnlyElement;
Expand All @@ -41,13 +44,15 @@
* {@code SomeInterfaceOne} and {@code SomeInterfaceTwo}.
*/
@PublicAPI(usage = ACCESS)
public final class JavaTypeVariable implements JavaType, HasUpperBounds {
public final class JavaTypeVariable<OWNER extends HasDescription> implements JavaType, HasOwner<OWNER>, HasUpperBounds {
private final String name;
private final OWNER owner;
private List<JavaType> upperBounds = emptyList();
private JavaClass erasure;

JavaTypeVariable(String name, JavaClass erasure) {
JavaTypeVariable(String name, OWNER owner, JavaClass erasure) {
this.name = name;
this.owner = owner;
this.erasure = erasure;
}

Expand All @@ -67,7 +72,31 @@ public String getName() {
}

/**
* @see #getUpperBounds()
* This method is simply an alias for {@link #getOwner()} that is more familiar to users
* of the Java Reflection API.
*
* @see TypeVariable#getGenericDeclaration()
*/
@PublicAPI(usage = ACCESS)
public OWNER getGenericDeclaration() {
return getOwner();
}

/**
* @return The 'owner' of this type parameter, i.e. the Java object that declared this
* {@link TypeVariable} as a type parameter. For type parameter {@code T} of
* {@code SomeClass<T>} this would be the {@code JavaClass} representing {@code SomeClass}
*/
@Override
public OWNER getOwner() {
return owner;
}

/**
* This method is simply an alias for {@link #getUpperBounds()} that is more familiar to users
* of the Java Reflection API.
*
* @see TypeVariable#getBounds()
*/
@PublicAPI(usage = ACCESS)
public List<JavaType> getBounds() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public class JavaWildcardType implements JavaType, HasUpperBounds {
private final List<JavaType> lowerBounds;
private final JavaClass erasure;

JavaWildcardType(JavaWildcardTypeBuilder builder) {
JavaWildcardType(JavaWildcardTypeBuilder<?> builder) {
upperBounds = builder.getUpperBounds();
lowerBounds = builder.getLowerBounds();
erasure = builder.getUnboundErasureType(upperBounds);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class ClassFileImportRecord {
private static final Logger LOG = LoggerFactory.getLogger(ClassFileImportRecord.class);

private static final TypeParametersBuilder NO_TYPE_PARAMETERS =
new TypeParametersBuilder(Collections.<JavaTypeParameterBuilder>emptySet());
new TypeParametersBuilder(Collections.<JavaTypeParameterBuilder<JavaClass>>emptySet());

private final Map<String, JavaClass> classes = new HashMap<>();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ public Set<JavaClass> createInterfaces(JavaClass owner) {
}

@Override
public List<JavaTypeVariable> createTypeParameters(JavaClass owner) {
public List<JavaTypeVariable<JavaClass>> createTypeParameters(JavaClass owner) {
TypeParametersBuilder typeParametersBuilder = importRecord.getTypeParameterBuildersFor(owner.getName());
return typeParametersBuilder.build(owner, classes.byTypeName());
}
Expand Down
Loading

0 comments on commit 44698c9

Please sign in to comment.