Skip to content

Commit 44698c9

Browse files
authored
Add JavaClass type parameters to dependencies #484
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.
2 parents 99841dc + 301ccd8 commit 44698c9

File tree

22 files changed

+361
-123
lines changed

22 files changed

+361
-123
lines changed

archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/service/ServiceHelper.java

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
11
package com.tngtech.archunit.example.layers.service;
22

3+
import java.util.Map;
4+
import java.util.Set;
5+
6+
import com.tngtech.archunit.example.layers.controller.SomeUtility;
7+
import com.tngtech.archunit.example.layers.controller.one.SomeEnum;
38
import com.tngtech.archunit.example.layers.security.Secured;
49

510
/**
611
* Well modelled code always has lots of 'helpers' ;-)
712
*/
8-
public class ServiceHelper {
13+
@SuppressWarnings("unused")
14+
public class ServiceHelper<
15+
TYPE_PARAMETER_VIOLATING_LAYER_RULE extends SomeUtility,
16+
ANOTHER_TYPE_PARAMETER_VIOLATING_LAYER_RULE extends Map<?, Set<? super SomeEnum>>> {
17+
918
public Object insecure = new Object();
1019
@Secured
1120
public Object properlySecured = new Object();

archunit-integration-test/src/test/java/com/tngtech/archunit/integration/ExamplesIntegrationTest.java

+7
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,7 @@
166166
import static com.tngtech.archunit.testutils.ExpectedDependency.field;
167167
import static com.tngtech.archunit.testutils.ExpectedDependency.inheritanceFrom;
168168
import static com.tngtech.archunit.testutils.ExpectedDependency.method;
169+
import static com.tngtech.archunit.testutils.ExpectedDependency.typeParameter;
169170
import static com.tngtech.archunit.testutils.ExpectedLocation.javaClass;
170171
import static com.tngtech.archunit.testutils.ExpectedNaming.simpleNameOf;
171172
import static com.tngtech.archunit.testutils.ExpectedNaming.simpleNameOfAnonymousClassOf;
@@ -739,6 +740,8 @@ Stream<DynamicTest> LayerDependencyRulesTest() {
739740
.by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController)
740741
.toMethod(UseCaseTwoController.class, doSomethingTwo)
741742
.inLine(25).asDependency())
743+
.by(typeParameter(ServiceHelper.class, "TYPE_PARAMETER_VIOLATING_LAYER_RULE").dependingOn(SomeUtility.class))
744+
.by(typeParameter(ServiceHelper.class, "ANOTHER_TYPE_PARAMETER_VIOLATING_LAYER_RULE").dependingOn(SomeEnum.class))
742745
.by(method(ServiceViolatingLayerRules.class, dependentMethod).withParameter(UseCaseTwoController.class))
743746
.by(method(ServiceViolatingLayerRules.class, dependentMethod).withReturnType(SomeGuiController.class))
744747
.by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).withParameter(UseCaseTwoController[].class))
@@ -796,6 +799,8 @@ Stream<DynamicTest> LayerDependencyRulesTest() {
796799
.by(callFromMethod(ServiceViolatingLayerRules.class, illegalAccessToController)
797800
.toMethod(UseCaseTwoController.class, doSomethingTwo)
798801
.inLine(25).asDependency())
802+
.by(typeParameter(ServiceHelper.class, "TYPE_PARAMETER_VIOLATING_LAYER_RULE").dependingOn(SomeUtility.class))
803+
.by(typeParameter(ServiceHelper.class, "ANOTHER_TYPE_PARAMETER_VIOLATING_LAYER_RULE").dependingOn(SomeEnum.class))
799804
.by(method(ServiceViolatingLayerRules.class, dependentMethod).withParameter(UseCaseTwoController.class))
800805
.by(method(ServiceViolatingLayerRules.class, dependentMethod).withReturnType(SomeGuiController.class))
801806
.by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod).withParameter(UseCaseTwoController[].class))
@@ -863,6 +868,8 @@ Stream<DynamicTest> LayeredArchitectureTest() {
863868
.inLine(27)
864869
.asDependency())
865870

871+
.by(typeParameter(ServiceHelper.class, "TYPE_PARAMETER_VIOLATING_LAYER_RULE").dependingOn(SomeUtility.class))
872+
.by(typeParameter(ServiceHelper.class, "ANOTHER_TYPE_PARAMETER_VIOLATING_LAYER_RULE").dependingOn(SomeEnum.class))
866873
.by(method(ServiceViolatingLayerRules.class, dependentMethod).withParameter(UseCaseTwoController.class))
867874
.by(method(ServiceViolatingLayerRules.class, dependentMethod).withReturnType(SomeGuiController.class))
868875
.by(method(ServiceViolatingLayerRules.class, dependentOnComponentTypeMethod)

archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedDependency.java

+19
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ public static InheritanceCreator inheritanceFrom(Class<?> clazz) {
2525
return new InheritanceCreator(clazz);
2626
}
2727

28+
public static TypeParameterCreator typeParameter(Class<?> clazz, String typeParameterName) {
29+
return new TypeParameterCreator(clazz, typeParameterName);
30+
}
31+
2832
public static AnnotationDependencyCreator annotatedClass(Class<?> clazz) {
2933
return new AnnotationDependencyCreator(clazz);
3034
}
@@ -85,6 +89,21 @@ public ExpectedDependency implementing(Class<?> anInterface) {
8589
}
8690
}
8791

92+
public static class TypeParameterCreator {
93+
private final Class<?> clazz;
94+
private final String typeParameterName;
95+
96+
private TypeParameterCreator(Class<?> clazz, String typeParameterName) {
97+
this.clazz = clazz;
98+
this.typeParameterName = typeParameterName;
99+
}
100+
101+
public ExpectedDependency dependingOn(Class<?> typeParameterDependency) {
102+
return new ExpectedDependency(clazz, typeParameterDependency,
103+
getDependencyPattern(clazz.getName(), "has type parameter '" + typeParameterName + "' depending on", typeParameterDependency.getName(), 0));
104+
}
105+
}
106+
88107
public static class AccessCreator {
89108
private final Class<?> originClass;
90109

archunit/src/main/java/com/tngtech/archunit/core/domain/Dependency.java

+14-9
Original file line numberDiff line numberDiff line change
@@ -120,26 +120,31 @@ static Set<Dependency> tryCreateFromInstanceofCheck(InstanceofCheck instanceofCh
120120
}
121121

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

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

132-
private static Origin findSuitableOrigin(JavaAnnotation<?> annotation) {
133-
Object annotatedElement = annotation.getAnnotatedElement();
134-
if (annotatedElement instanceof JavaMember) {
135-
JavaMember member = (JavaMember) annotatedElement;
132+
static Set<Dependency> tryCreateFromTypeParameter(JavaTypeVariable<?> typeParameter, JavaClass typeParameterDependency) {
133+
String dependencyType = "has type parameter '" + typeParameter.getName() + "' depending on";
134+
Origin origin = findSuitableOrigin(typeParameter, typeParameter.getOwner());
135+
return tryCreateDependency(origin.originClass, origin.originDescription, dependencyType, typeParameterDependency);
136+
}
137+
138+
private static Origin findSuitableOrigin(Object dependencyCause, Object originCandidate) {
139+
if (originCandidate instanceof JavaMember) {
140+
JavaMember member = (JavaMember) originCandidate;
136141
return new Origin(member.getOwner(), member.getDescription());
137142
}
138-
if (annotatedElement instanceof JavaClass) {
139-
JavaClass clazz = (JavaClass) annotatedElement;
143+
if (originCandidate instanceof JavaClass) {
144+
JavaClass clazz = (JavaClass) originCandidate;
140145
return new Origin(clazz, clazz.getDescription());
141146
}
142-
throw new IllegalStateException("Could not find suitable dependency origin for " + annotation);
147+
throw new IllegalStateException("Could not find suitable dependency origin for " + dependencyCause);
143148
}
144149

145150
private static Set<Dependency> tryCreateDependencyFromJavaMember(JavaMember origin, String dependencyType, JavaClass target) {

archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,11 @@ public static InstanceofCheck createInstanceofCheck(JavaCodeUnit codeUnit, JavaC
150150
return InstanceofCheck.from(codeUnit, target, lineNumber);
151151
}
152152

153-
public static JavaTypeVariable createTypeVariable(String name, JavaClass erasure) {
154-
return new JavaTypeVariable(name, erasure);
153+
public static <OWNER extends HasDescription> JavaTypeVariable<OWNER> createTypeVariable(String name, OWNER owner, JavaClass erasure) {
154+
return new JavaTypeVariable<>(name, owner, erasure);
155155
}
156156

157-
public static void completeTypeVariable(JavaTypeVariable variable, List<JavaType> upperBounds) {
157+
public static void completeTypeVariable(JavaTypeVariable<?> variable, List<JavaType> upperBounds) {
158158
variable.setUpperBounds(upperBounds);
159159
}
160160

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

167-
public static JavaWildcardType createWildcardType(JavaWildcardTypeBuilder builder) {
167+
public static JavaWildcardType createWildcardType(JavaWildcardTypeBuilder<?> builder) {
168168
return new JavaWildcardType(builder);
169169
}
170170
}

archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public interface ImportContext {
2828

2929
Set<JavaClass> createInterfaces(JavaClass owner);
3030

31-
List<JavaTypeVariable> createTypeParameters(JavaClass owner);
31+
List<JavaTypeVariable<JavaClass>> createTypeParameters(JavaClass owner);
3232

3333
Set<JavaField> createFields(JavaClass owner);
3434

archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ public class JavaClass implements JavaType, HasName.AndFullName, HasAnnotations<
7272
private final boolean isAnonymousClass;
7373
private final boolean isMemberClass;
7474
private final Set<JavaModifier> modifiers;
75-
private List<JavaTypeVariable> typeParameters = emptyList();
75+
private List<JavaTypeVariable<JavaClass>> typeParameters = emptyList();
7676
private final Supplier<Class<?>> reflectSupplier;
7777
private Set<JavaField> fields = emptySet();
7878
private Set<JavaCodeUnit> codeUnits = emptySet();
@@ -644,7 +644,7 @@ public Optional<JavaAnnotation<JavaClass>> tryGetAnnotationOfType(String typeNam
644644
}
645645

646646
@PublicAPI(usage = ACCESS)
647-
public List<JavaTypeVariable> getTypeParameters() {
647+
public List<JavaTypeVariable<JavaClass>> getTypeParameters() {
648648
return typeParameters;
649649
}
650650

archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDependencies.java

+49
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import com.tngtech.archunit.core.domain.properties.HasAnnotations;
2626

2727
import static com.google.common.base.Suppliers.memoize;
28+
import static com.google.common.collect.Iterables.concat;
2829

2930
class JavaClassDependencies {
3031
private final JavaClass javaClass;
@@ -49,6 +50,7 @@ public Set<Dependency> get() {
4950
result.addAll(constructorParameterDependenciesFromSelf());
5051
result.addAll(annotationDependenciesFromSelf());
5152
result.addAll(instanceofCheckDependenciesFromSelf());
53+
result.addAll(typeParameterDependenciesFromSelf());
5254
return result.build();
5355
}
5456
});
@@ -139,6 +141,53 @@ private Set<Dependency> instanceofCheckDependenciesFromSelf() {
139141
return result.build();
140142
}
141143

144+
private Set<Dependency> typeParameterDependenciesFromSelf() {
145+
ImmutableSet.Builder<Dependency> result = ImmutableSet.builder();
146+
for (JavaTypeVariable<?> typeVariable : javaClass.getTypeParameters()) {
147+
result.addAll(getDependenciesFromTypeParameter(typeVariable));
148+
}
149+
return result.build();
150+
}
151+
152+
private Set<Dependency> getDependenciesFromTypeParameter(JavaTypeVariable<?> typeVariable) {
153+
ImmutableSet.Builder<Dependency> dependenciesBuilder = ImmutableSet.builder();
154+
for (JavaType bound : typeVariable.getUpperBounds()) {
155+
for (JavaClass typeParameterDependency : dependenciesOfType(bound)) {
156+
dependenciesBuilder.addAll(Dependency.tryCreateFromTypeParameter(typeVariable, typeParameterDependency));
157+
}
158+
}
159+
return dependenciesBuilder.build();
160+
}
161+
162+
private static Iterable<JavaClass> dependenciesOfType(JavaType javaType) {
163+
ImmutableSet.Builder<JavaClass> result = ImmutableSet.builder();
164+
if (javaType instanceof JavaClass) {
165+
result.add((JavaClass) javaType);
166+
} else if (javaType instanceof JavaParameterizedType) {
167+
result.addAll(dependenciesOfParameterizedType((JavaParameterizedType) javaType));
168+
} else if (javaType instanceof JavaWildcardType) {
169+
result.addAll(dependenciesOfWildcardType((JavaWildcardType) javaType));
170+
}
171+
return result.build();
172+
}
173+
174+
private static Set<JavaClass> dependenciesOfParameterizedType(JavaParameterizedType parameterizedType) {
175+
ImmutableSet.Builder<JavaClass> result = ImmutableSet.<JavaClass>builder()
176+
.add(parameterizedType.toErasure());
177+
for (JavaType typeArgument : parameterizedType.getActualTypeArguments()) {
178+
result.addAll(dependenciesOfType(typeArgument));
179+
}
180+
return result.build();
181+
}
182+
183+
private static Set<JavaClass> dependenciesOfWildcardType(JavaWildcardType javaType) {
184+
ImmutableSet.Builder<JavaClass> result = ImmutableSet.builder();
185+
for (JavaType bound : concat(javaType.getUpperBounds(), javaType.getLowerBounds())) {
186+
result.addAll(dependenciesOfType(bound));
187+
}
188+
return result.build();
189+
}
190+
142191
private <T extends HasDescription & HasAnnotations<?>> Set<Dependency> annotationDependencies(Set<T> annotatedObjects) {
143192
ImmutableSet.Builder<Dependency> result = ImmutableSet.builder();
144193
for (T annotated : annotatedObjects) {

archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java

+32-3
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@
1515
*/
1616
package com.tngtech.archunit.core.domain;
1717

18+
import java.lang.reflect.TypeVariable;
1819
import java.util.List;
1920

2021
import com.google.common.base.Joiner;
2122
import com.google.common.collect.FluentIterable;
2223
import com.tngtech.archunit.PublicAPI;
24+
import com.tngtech.archunit.base.HasDescription;
25+
import com.tngtech.archunit.core.domain.properties.HasOwner;
2326
import com.tngtech.archunit.core.domain.properties.HasUpperBounds;
2427

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

49-
JavaTypeVariable(String name, JavaClass erasure) {
53+
JavaTypeVariable(String name, OWNER owner, JavaClass erasure) {
5054
this.name = name;
55+
this.owner = owner;
5156
this.erasure = erasure;
5257
}
5358

@@ -67,7 +72,31 @@ public String getName() {
6772
}
6873

6974
/**
70-
* @see #getUpperBounds()
75+
* This method is simply an alias for {@link #getOwner()} that is more familiar to users
76+
* of the Java Reflection API.
77+
*
78+
* @see TypeVariable#getGenericDeclaration()
79+
*/
80+
@PublicAPI(usage = ACCESS)
81+
public OWNER getGenericDeclaration() {
82+
return getOwner();
83+
}
84+
85+
/**
86+
* @return The 'owner' of this type parameter, i.e. the Java object that declared this
87+
* {@link TypeVariable} as a type parameter. For type parameter {@code T} of
88+
* {@code SomeClass<T>} this would be the {@code JavaClass} representing {@code SomeClass}
89+
*/
90+
@Override
91+
public OWNER getOwner() {
92+
return owner;
93+
}
94+
95+
/**
96+
* This method is simply an alias for {@link #getUpperBounds()} that is more familiar to users
97+
* of the Java Reflection API.
98+
*
99+
* @see TypeVariable#getBounds()
71100
*/
72101
@PublicAPI(usage = ACCESS)
73102
public List<JavaType> getBounds() {

archunit/src/main/java/com/tngtech/archunit/core/domain/JavaWildcardType.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public class JavaWildcardType implements JavaType, HasUpperBounds {
4646
private final List<JavaType> lowerBounds;
4747
private final JavaClass erasure;
4848

49-
JavaWildcardType(JavaWildcardTypeBuilder builder) {
49+
JavaWildcardType(JavaWildcardTypeBuilder<?> builder) {
5050
upperBounds = builder.getUpperBounds();
5151
lowerBounds = builder.getLowerBounds();
5252
erasure = builder.getUnboundErasureType(upperBounds);

archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class ClassFileImportRecord {
4141
private static final Logger LOG = LoggerFactory.getLogger(ClassFileImportRecord.class);
4242

4343
private static final TypeParametersBuilder NO_TYPE_PARAMETERS =
44-
new TypeParametersBuilder(Collections.<JavaTypeParameterBuilder>emptySet());
44+
new TypeParametersBuilder(Collections.<JavaTypeParameterBuilder<JavaClass>>emptySet());
4545

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

archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ public Set<JavaClass> createInterfaces(JavaClass owner) {
237237
}
238238

239239
@Override
240-
public List<JavaTypeVariable> createTypeParameters(JavaClass owner) {
240+
public List<JavaTypeVariable<JavaClass>> createTypeParameters(JavaClass owner) {
241241
TypeParametersBuilder typeParametersBuilder = importRecord.getTypeParameterBuildersFor(owner.getName());
242242
return typeParametersBuilder.build(owner, classes.byTypeName());
243243
}

0 commit comments

Comments
 (0)