diff --git a/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/service/ServiceHelper.java b/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/service/ServiceHelper.java index 1210abe8a4..f72fb50ba5 100644 --- a/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/service/ServiceHelper.java +++ b/archunit-example/example-plain/src/main/java/com/tngtech/archunit/example/layers/service/ServiceHelper.java @@ -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>> { + public Object insecure = new Object(); @Secured public Object properlySecured = new Object(); diff --git a/archunit-integration-test/src/test/java/com/tngtech/archunit/integration/ExamplesIntegrationTest.java b/archunit-integration-test/src/test/java/com/tngtech/archunit/integration/ExamplesIntegrationTest.java index 907d20b6bb..f0727d8c7d 100644 --- a/archunit-integration-test/src/test/java/com/tngtech/archunit/integration/ExamplesIntegrationTest.java +++ b/archunit-integration-test/src/test/java/com/tngtech/archunit/integration/ExamplesIntegrationTest.java @@ -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; @@ -739,6 +740,8 @@ Stream 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)) @@ -796,6 +799,8 @@ Stream 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)) @@ -863,6 +868,8 @@ Stream 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) diff --git a/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedDependency.java b/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedDependency.java index 96d42baa74..7a19fce21d 100644 --- a/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedDependency.java +++ b/archunit-integration-test/src/test/java/com/tngtech/archunit/testutils/ExpectedDependency.java @@ -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); } @@ -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; diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/Dependency.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/Dependency.java index 950f2c55ed..e3de3ea06e 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/Dependency.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/Dependency.java @@ -120,26 +120,31 @@ static Set tryCreateFromInstanceofCheck(InstanceofCheck instanceofCh } static Set 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 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 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 tryCreateDependencyFromJavaMember(JavaMember origin, String dependencyType, JavaClass target) { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java index 541fd42a62..61b468e376 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/DomainObjectCreationContext.java @@ -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 JavaTypeVariable createTypeVariable(String name, OWNER owner, JavaClass erasure) { + return new JavaTypeVariable<>(name, owner, erasure); } - public static void completeTypeVariable(JavaTypeVariable variable, List upperBounds) { + public static void completeTypeVariable(JavaTypeVariable variable, List upperBounds) { variable.setUpperBounds(upperBounds); } @@ -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); } } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java index ff179b9c62..2e8e7a8248 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/ImportContext.java @@ -28,7 +28,7 @@ public interface ImportContext { Set createInterfaces(JavaClass owner); - List createTypeParameters(JavaClass owner); + List> createTypeParameters(JavaClass owner); Set createFields(JavaClass owner); diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java index 9a6fe851d2..33666da087 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java @@ -72,7 +72,7 @@ public class JavaClass implements JavaType, HasName.AndFullName, HasAnnotations< private final boolean isAnonymousClass; private final boolean isMemberClass; private final Set modifiers; - private List typeParameters = emptyList(); + private List> typeParameters = emptyList(); private final Supplier> reflectSupplier; private Set fields = emptySet(); private Set codeUnits = emptySet(); @@ -644,7 +644,7 @@ public Optional> tryGetAnnotationOfType(String typeNam } @PublicAPI(usage = ACCESS) - public List getTypeParameters() { + public List> getTypeParameters() { return typeParameters; } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDependencies.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDependencies.java index 902dd31f6a..8038cd3b76 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDependencies.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClassDependencies.java @@ -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; @@ -49,6 +50,7 @@ public Set get() { result.addAll(constructorParameterDependenciesFromSelf()); result.addAll(annotationDependenciesFromSelf()); result.addAll(instanceofCheckDependenciesFromSelf()); + result.addAll(typeParameterDependenciesFromSelf()); return result.build(); } }); @@ -139,6 +141,53 @@ private Set instanceofCheckDependenciesFromSelf() { return result.build(); } + private Set typeParameterDependenciesFromSelf() { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (JavaTypeVariable typeVariable : javaClass.getTypeParameters()) { + result.addAll(getDependenciesFromTypeParameter(typeVariable)); + } + return result.build(); + } + + private Set getDependenciesFromTypeParameter(JavaTypeVariable typeVariable) { + ImmutableSet.Builder 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 dependenciesOfType(JavaType javaType) { + ImmutableSet.Builder 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 dependenciesOfParameterizedType(JavaParameterizedType parameterizedType) { + ImmutableSet.Builder result = ImmutableSet.builder() + .add(parameterizedType.toErasure()); + for (JavaType typeArgument : parameterizedType.getActualTypeArguments()) { + result.addAll(dependenciesOfType(typeArgument)); + } + return result.build(); + } + + private static Set dependenciesOfWildcardType(JavaWildcardType javaType) { + ImmutableSet.Builder result = ImmutableSet.builder(); + for (JavaType bound : concat(javaType.getUpperBounds(), javaType.getLowerBounds())) { + result.addAll(dependenciesOfType(bound)); + } + return result.build(); + } + private > Set annotationDependencies(Set annotatedObjects) { ImmutableSet.Builder result = ImmutableSet.builder(); for (T annotated : annotatedObjects) { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java index 9b1c940917..b0f1af0b54 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaTypeVariable.java @@ -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; @@ -41,13 +44,15 @@ * {@code SomeInterfaceOne} and {@code SomeInterfaceTwo}. */ @PublicAPI(usage = ACCESS) -public final class JavaTypeVariable implements JavaType, HasUpperBounds { +public final class JavaTypeVariable implements JavaType, HasOwner, HasUpperBounds { private final String name; + private final OWNER owner; private List 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; } @@ -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} 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 getBounds() { diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaWildcardType.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaWildcardType.java index ed18ae613a..101b84152f 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaWildcardType.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaWildcardType.java @@ -46,7 +46,7 @@ public class JavaWildcardType implements JavaType, HasUpperBounds { private final List lowerBounds; private final JavaClass erasure; - JavaWildcardType(JavaWildcardTypeBuilder builder) { + JavaWildcardType(JavaWildcardTypeBuilder builder) { upperBounds = builder.getUpperBounds(); lowerBounds = builder.getLowerBounds(); erasure = builder.getUnboundErasureType(upperBounds); diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java index eeaf17fc19..bd7f2bf211 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassFileImportRecord.java @@ -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.emptySet()); + new TypeParametersBuilder(Collections.>emptySet()); private final Map classes = new HashMap<>(); diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java index e221305542..7f90453da7 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/ClassGraphCreator.java @@ -237,7 +237,7 @@ public Set createInterfaces(JavaClass owner) { } @Override - public List createTypeParameters(JavaClass owner) { + public List> createTypeParameters(JavaClass owner) { TypeParametersBuilder typeParametersBuilder = importRecord.getTypeParameterBuildersFor(owner.getName()); return typeParametersBuilder.build(owner, classes.byTypeName()); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java index 206efb0c8e..1903985aab 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/DomainBuilders.java @@ -505,64 +505,67 @@ JavaStaticInitializer construct(JavaStaticInitializerBuilder builder, ClassesByT } } - interface JavaTypeCreationProcess { - JavaType finish(Iterable allTypeParametersInContext, ClassesByTypeName classes); + interface JavaTypeCreationProcess { + JavaType finish(OWNER owner, Iterable> allTypeParametersInContext, ClassesByTypeName classes); } @Internal - public static final class JavaTypeParameterBuilder { + public static final class JavaTypeParameterBuilder { private final String name; - private final List upperBounds = new ArrayList<>(); + private final List> upperBounds = new ArrayList<>(); + private OWNER owner; private ClassesByTypeName importedClasses; JavaTypeParameterBuilder(String name) { this.name = checkNotNull(name); } - void addBound(JavaTypeCreationProcess bound) { + void addBound(JavaTypeCreationProcess bound) { upperBounds.add(bound); } - public JavaTypeVariable build(ClassesByTypeName importedClasses) { + public JavaTypeVariable build(OWNER owner, ClassesByTypeName importedClasses) { + this.owner = owner; this.importedClasses = importedClasses; - return createTypeVariable(name, this.importedClasses.get(Object.class.getName())); + return createTypeVariable(name, owner, this.importedClasses.get(Object.class.getName())); } String getName() { return name; } - public List getUpperBounds(Iterable allGenericParametersInContext) { - return buildJavaTypes(upperBounds, allGenericParametersInContext, importedClasses); + @SuppressWarnings("unchecked") // Iterable is covariant + public List getUpperBounds(Iterable> allGenericParametersInContext) { + return buildJavaTypes(upperBounds, owner, (Iterable>) allGenericParametersInContext, importedClasses); } } static class TypeParametersBuilder { - private final Collection typeParameterBuilders; + private final Collection> typeParameterBuilders; - TypeParametersBuilder(Collection typeParameterBuilders) { + TypeParametersBuilder(Collection> typeParameterBuilders) { this.typeParameterBuilders = typeParameterBuilders; } - public List build(JavaClass owner, ClassesByTypeName classesByTypeName) { + public List> build(JavaClass owner, ClassesByTypeName classesByTypeName) { if (typeParameterBuilders.isEmpty()) { return Collections.emptyList(); } - Map typeArgumentsToBuilders = new LinkedHashMap<>(); - for (JavaTypeParameterBuilder builder : typeParameterBuilders) { - typeArgumentsToBuilders.put(builder.build(classesByTypeName), builder); + Map, JavaTypeParameterBuilder> typeArgumentsToBuilders = new LinkedHashMap<>(); + for (JavaTypeParameterBuilder builder : typeParameterBuilders) { + typeArgumentsToBuilders.put(builder.build(owner, classesByTypeName), builder); } - Set allGenericParametersInContext = union(allTypeParametersInEnclosingClassesOf(owner), typeArgumentsToBuilders.keySet()); - for (Map.Entry typeParameterToBuilder : typeArgumentsToBuilders.entrySet()) { + Set> allGenericParametersInContext = union(allTypeParametersInEnclosingClassesOf(owner), typeArgumentsToBuilders.keySet()); + for (Map.Entry, JavaTypeParameterBuilder> typeParameterToBuilder : typeArgumentsToBuilders.entrySet()) { List upperBounds = typeParameterToBuilder.getValue().getUpperBounds(allGenericParametersInContext); completeTypeVariable(typeParameterToBuilder.getKey(), upperBounds); } return ImmutableList.copyOf(typeArgumentsToBuilders.keySet()); } - private Set allTypeParametersInEnclosingClassesOf(JavaClass javaClass) { - Set result = new HashSet<>(); + private Set> allTypeParametersInEnclosingClassesOf(JavaClass javaClass) { + Set> result = new HashSet<>(); while (javaClass.getEnclosingClass().isPresent()) { result.addAll(javaClass.getEnclosingClass().get().getTypeParameters()); javaClass = javaClass.getEnclosingClass().get(); @@ -571,43 +574,45 @@ private Set allTypeParametersInEnclosingClassesOf(JavaClass ja } } - interface JavaTypeBuilder { - JavaType build(Iterable allTypeParametersInContext, ClassesByTypeName importedClasses); + interface JavaTypeBuilder { + JavaType build(OWNER owner, Iterable> allTypeParametersInContext, ClassesByTypeName importedClasses); } @Internal - public static final class JavaWildcardTypeBuilder implements JavaTypeBuilder { - private final List lowerBoundCreationProcesses = new ArrayList<>(); - private final List upperBoundCreationProcesses = new ArrayList<>(); - private Iterable allTypeParametersInContext; + public static final class JavaWildcardTypeBuilder implements JavaTypeBuilder { + private final List> lowerBoundCreationProcesses = new ArrayList<>(); + private final List> upperBoundCreationProcesses = new ArrayList<>(); + private OWNER owner; + private Iterable> allTypeParametersInContext; private ClassesByTypeName importedClasses; JavaWildcardTypeBuilder() { } - public JavaWildcardTypeBuilder addLowerBound(JavaTypeCreationProcess boundCreationProcess) { + public JavaWildcardTypeBuilder addLowerBound(JavaTypeCreationProcess boundCreationProcess) { lowerBoundCreationProcesses.add(boundCreationProcess); return this; } - public JavaWildcardTypeBuilder addUpperBound(JavaTypeCreationProcess boundCreationProcess) { + public JavaWildcardTypeBuilder addUpperBound(JavaTypeCreationProcess boundCreationProcess) { upperBoundCreationProcesses.add(boundCreationProcess); return this; } @Override - public JavaWildcardType build(Iterable allTypeParametersInContext, ClassesByTypeName importedClasses) { + public JavaWildcardType build(OWNER owner, Iterable> allTypeParametersInContext, ClassesByTypeName importedClasses) { + this.owner = owner; this.allTypeParametersInContext = allTypeParametersInContext; this.importedClasses = importedClasses; return createWildcardType(this); } public List getUpperBounds() { - return buildJavaTypes(upperBoundCreationProcesses, allTypeParametersInContext, importedClasses); + return buildJavaTypes(upperBoundCreationProcesses, owner, allTypeParametersInContext, importedClasses); } public List getLowerBounds() { - return buildJavaTypes(lowerBoundCreationProcesses, allTypeParametersInContext, importedClasses); + return buildJavaTypes(lowerBoundCreationProcesses, owner, allTypeParametersInContext, importedClasses); } public JavaClass getUnboundErasureType(List upperBounds) { @@ -615,21 +620,21 @@ public JavaClass getUnboundErasureType(List upperBounds) { } } - static class JavaParameterizedTypeBuilder implements JavaTypeBuilder { + static class JavaParameterizedTypeBuilder implements JavaTypeBuilder { private final JavaClassDescriptor type; - private final List typeArgumentCreationProcesses = new ArrayList<>(); + private final List> typeArgumentCreationProcesses = new ArrayList<>(); JavaParameterizedTypeBuilder(JavaClassDescriptor type) { this.type = type; } - void addTypeArgument(JavaTypeCreationProcess typeCreationProcess) { + void addTypeArgument(JavaTypeCreationProcess typeCreationProcess) { typeArgumentCreationProcesses.add(typeCreationProcess); } @Override - public JavaParameterizedType build(Iterable allTypeParametersInContext, ClassesByTypeName classes) { - List typeArguments = buildJavaTypes(typeArgumentCreationProcesses, allTypeParametersInContext, classes); + public JavaParameterizedType build(OWNER owner, Iterable> allTypeParametersInContext, ClassesByTypeName classes) { + List typeArguments = buildJavaTypes(typeArgumentCreationProcesses, owner, allTypeParametersInContext, classes); return new ImportedParameterizedType(classes.get(type.getFullyQualifiedClassName()), typeArguments); } @@ -638,10 +643,10 @@ String getTypeName() { } } - private static List buildJavaTypes(List typeCreationProcesses, Iterable allGenericParametersInContext, ClassesByTypeName classes) { + private static List buildJavaTypes(List> typeCreationProcesses, OWNER owner, Iterable> allGenericParametersInContext, ClassesByTypeName classes) { ImmutableList.Builder result = ImmutableList.builder(); - for (JavaTypeCreationProcess typeCreationProcess : typeCreationProcesses) { - result.add(typeCreationProcess.finish(allGenericParametersInContext, classes)); + for (JavaTypeCreationProcess typeCreationProcess : typeCreationProcesses) { + result.add(typeCreationProcess.finish(owner, allGenericParametersInContext, classes)); } return result.build(); } diff --git a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java index 1b0ad6ea62..6c89e6fa4b 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/importer/JavaGenericTypeImporter.java @@ -20,6 +20,7 @@ import com.google.common.base.Function; import com.google.common.base.Functions; +import com.tngtech.archunit.base.HasDescription; import com.tngtech.archunit.core.domain.JavaClass; import com.tngtech.archunit.core.domain.JavaClassDescriptor; import com.tngtech.archunit.core.domain.JavaType; @@ -58,7 +59,7 @@ static void parseAsmTypeSignature(String signature, DeclarationHandler declarati private static class JavaTypeVariableProcessor extends SignatureVisitor { private static final BoundProcessor boundProcessor = new BoundProcessor(); - private final List typeParameterBuilders = new ArrayList<>(); + private final List> typeParameterBuilders = new ArrayList<>(); JavaTypeVariableProcessor() { super(ASM_API_VERSION); @@ -67,7 +68,7 @@ private static class JavaTypeVariableProcessor extends SignatureVisitor { @Override public void visitFormalTypeParameter(String name) { log.trace("Encountered type parameter {}", name); - JavaTypeParameterBuilder type = new JavaTypeParameterBuilder(name); + JavaTypeParameterBuilder type = new JavaTypeParameterBuilder<>(name); boundProcessor.setCurrentType(type); typeParameterBuilders.add(type); } @@ -83,14 +84,14 @@ public SignatureVisitor visitInterfaceBound() { } private static class BoundProcessor extends SignatureVisitor { - private JavaTypeParameterBuilder currentType; - private JavaParameterizedTypeBuilder currentBound; + private JavaTypeParameterBuilder currentType; + private JavaParameterizedTypeBuilder currentBound; BoundProcessor() { super(ASM_API_VERSION); } - void setCurrentType(JavaTypeParameterBuilder type) { + void setCurrentType(JavaTypeParameterBuilder type) { this.currentType = type; } @@ -98,20 +99,20 @@ void setCurrentType(JavaTypeParameterBuilder type) { public void visitClassType(String internalObjectName) { JavaClassDescriptor type = JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName); log.trace("Encountered upper bound for {}: Class type {}", currentType.getName(), type.getFullyQualifiedClassName()); - this.currentBound = new JavaParameterizedTypeBuilder(type); - currentType.addBound(new NewJavaTypeCreationProcess(this.currentBound)); + this.currentBound = new JavaParameterizedTypeBuilder<>(type); + currentType.addBound(new NewJavaTypeCreationProcess<>(this.currentBound)); } @Override public void visitTypeArgument() { log.trace("Encountered wildcard for {}", currentBound.getTypeName()); - currentBound.addTypeArgument(new NewJavaTypeCreationProcess(new JavaWildcardTypeBuilder())); + currentBound.addTypeArgument(new NewJavaTypeCreationProcess<>(new JavaWildcardTypeBuilder())); } @Override public void visitTypeVariable(String name) { log.trace("Encountered upper bound for {}: Type variable {}", currentType.getName(), name); - currentType.addBound(new ReferenceCreationProcess(name, ReferenceCreationProcess.JavaTypeVariableFinisher.IDENTITY)); + currentType.addBound(new ReferenceCreationProcess(name, ReferenceCreationProcess.JavaTypeVariableFinisher.IDENTITY)); } @Override @@ -121,20 +122,20 @@ public SignatureVisitor visitTypeArgument(char wildcard) { } } - private static class NewJavaTypeCreationProcess implements JavaTypeCreationProcess { - private final JavaTypeBuilder builder; + private static class NewJavaTypeCreationProcess implements JavaTypeCreationProcess { + private final JavaTypeBuilder builder; - NewJavaTypeCreationProcess(JavaTypeBuilder builder) { + NewJavaTypeCreationProcess(JavaTypeBuilder builder) { this.builder = builder; } @Override - public JavaType finish(Iterable allTypeParametersInContext, ClassesByTypeName classes) { - return builder.build(allTypeParametersInContext, classes); + public JavaType finish(OWNER owner, Iterable> allTypeParametersInContext, ClassesByTypeName classes) { + return builder.build(owner, allTypeParametersInContext, classes); } } - private static class ReferenceCreationProcess implements JavaTypeCreationProcess { + private static class ReferenceCreationProcess implements JavaTypeCreationProcess { private final String typeVariableName; private final JavaTypeVariableFinisher finisher; @@ -144,18 +145,18 @@ private static class ReferenceCreationProcess implements JavaTypeCreationProcess } @Override - public JavaType finish(Iterable allTypeParametersInContext, ClassesByTypeName classes) { - return finisher.finish(createTypeVariable(allTypeParametersInContext, classes), classes); + public JavaType finish(OWNER owner, Iterable> allTypeParametersInContext, ClassesByTypeName classes) { + return finisher.finish(createTypeVariable(owner, allTypeParametersInContext, classes), classes); } - private JavaType createTypeVariable(Iterable allTypeParametersInContext, ClassesByTypeName classes) { - for (JavaTypeVariable existingTypeVariable : allTypeParametersInContext) { + private JavaType createTypeVariable(OWNER owner, Iterable> allTypeParametersInContext, ClassesByTypeName classes) { + for (JavaTypeVariable existingTypeVariable : allTypeParametersInContext) { if (existingTypeVariable.getName().equals(typeVariableName)) { return existingTypeVariable; } } // type variables can be missing from the import context -> create a simple unbound type variable since we have no more information - return new JavaTypeParameterBuilder(typeVariableName).build(classes); + return new JavaTypeParameterBuilder<>(typeVariableName).build(owner, classes); } abstract static class JavaTypeVariableFinisher { @@ -194,6 +195,7 @@ String getFinishedName(String name) { private static class TypeArgumentProcessor extends SignatureVisitor { private static final Function TO_ARRAY_TYPE = new Function() { @Override + @SuppressWarnings("ConstantConditions") // we never return null by convention public JavaClassDescriptor apply(JavaClassDescriptor input) { return input.toArrayDescriptor(); } @@ -213,15 +215,15 @@ String getFinishedName(String name) { }; private final TypeArgumentType typeArgumentType; - private final JavaParameterizedTypeBuilder parameterizedType; + private final JavaParameterizedTypeBuilder parameterizedType; private final Function typeMapping; private final ReferenceCreationProcess.JavaTypeVariableFinisher typeVariableFinisher; - private JavaParameterizedTypeBuilder currentTypeArgument; + private JavaParameterizedTypeBuilder currentTypeArgument; TypeArgumentProcessor( TypeArgumentType typeArgumentType, - JavaParameterizedTypeBuilder parameterizedType, + JavaParameterizedTypeBuilder parameterizedType, Function typeMapping, ReferenceCreationProcess.JavaTypeVariableFinisher typeVariableFinisher) { super(ASM_API_VERSION); @@ -232,17 +234,18 @@ String getFinishedName(String name) { } @Override + @SuppressWarnings("ConstantConditions") // we never return null by convention public void visitClassType(String internalObjectName) { JavaClassDescriptor type = typeMapping.apply(JavaClassDescriptorImporter.createFromAsmObjectTypeName(internalObjectName)); log.trace("Encountered {} for {}: Class type {}", typeArgumentType.description, parameterizedType.getTypeName(), type.getFullyQualifiedClassName()); - currentTypeArgument = new JavaParameterizedTypeBuilder(type); - typeArgumentType.addTypeArgumentToBuilder(parameterizedType, new NewJavaTypeCreationProcess(this.currentTypeArgument)); + currentTypeArgument = new JavaParameterizedTypeBuilder<>(type); + typeArgumentType.addTypeArgumentToBuilder(parameterizedType, new NewJavaTypeCreationProcess<>(this.currentTypeArgument)); } @Override public void visitTypeArgument() { log.trace("Encountered wildcard for {}", currentTypeArgument.getTypeName()); - currentTypeArgument.addTypeArgument(new NewJavaTypeCreationProcess(new JavaWildcardTypeBuilder())); + currentTypeArgument.addTypeArgument(new NewJavaTypeCreationProcess<>(new JavaWildcardTypeBuilder())); } @Override @@ -250,7 +253,7 @@ public void visitTypeVariable(String name) { if (log.isTraceEnabled()) { log.trace("Encountered {} for {}: Type variable {}", typeArgumentType.description, parameterizedType.getTypeName(), typeVariableFinisher.getFinishedName(name)); } - typeArgumentType.addTypeArgumentToBuilder(parameterizedType, new ReferenceCreationProcess(name, typeVariableFinisher)); + typeArgumentType.addTypeArgumentToBuilder(parameterizedType, new ReferenceCreationProcess(name, typeVariableFinisher)); } @Override @@ -265,7 +268,7 @@ public SignatureVisitor visitArrayType() { static TypeArgumentProcessor create( char identifier, - JavaParameterizedTypeBuilder parameterizedType, + JavaParameterizedTypeBuilder parameterizedType, Function typeMapping, ReferenceCreationProcess.JavaTypeVariableFinisher typeVariableFinisher) { @@ -289,27 +292,27 @@ private abstract static class TypeArgumentType { this.description = description; } - abstract void addTypeArgumentToBuilder(JavaParameterizedTypeBuilder parameterizedType, JavaTypeCreationProcess creationProcess); + abstract void addTypeArgumentToBuilder(JavaParameterizedTypeBuilder parameterizedType, JavaTypeCreationProcess creationProcess); } private static final TypeArgumentType PARAMETERIZED_TYPE = new TypeArgumentType("type argument") { @Override - void addTypeArgumentToBuilder(JavaParameterizedTypeBuilder parameterizedType, JavaTypeCreationProcess typeCreationProcess) { + void addTypeArgumentToBuilder(JavaParameterizedTypeBuilder parameterizedType, JavaTypeCreationProcess typeCreationProcess) { parameterizedType.addTypeArgument(typeCreationProcess); } }; private static final TypeArgumentType WILDCARD_WITH_UPPER_BOUND = new TypeArgumentType("wildcard with upper bound") { @Override - void addTypeArgumentToBuilder(JavaParameterizedTypeBuilder parameterizedType, JavaTypeCreationProcess typeCreationProcess) { - parameterizedType.addTypeArgument(new NewJavaTypeCreationProcess(new JavaWildcardTypeBuilder().addUpperBound(typeCreationProcess))); + void addTypeArgumentToBuilder(JavaParameterizedTypeBuilder parameterizedType, JavaTypeCreationProcess typeCreationProcess) { + parameterizedType.addTypeArgument(new NewJavaTypeCreationProcess<>(new JavaWildcardTypeBuilder().addUpperBound(typeCreationProcess))); } }; private static final TypeArgumentType WILDCARD_WITH_LOWER_BOUND = new TypeArgumentType("wildcard with lower bound") { @Override - void addTypeArgumentToBuilder(JavaParameterizedTypeBuilder parameterizedType, JavaTypeCreationProcess typeCreationProcess) { - parameterizedType.addTypeArgument(new NewJavaTypeCreationProcess(new JavaWildcardTypeBuilder().addLowerBound(typeCreationProcess))); + void addTypeArgumentToBuilder(JavaParameterizedTypeBuilder parameterizedType, JavaTypeCreationProcess typeCreationProcess) { + parameterizedType.addTypeArgument(new NewJavaTypeCreationProcess<>(new JavaWildcardTypeBuilder().addLowerBound(typeCreationProcess))); } }; } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/DependencyTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/DependencyTest.java index 0dd11a0225..112b1c29c5 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/DependencyTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/DependencyTest.java @@ -257,6 +257,24 @@ public void Dependency_from_member_annotation_member(JavaMember annotatedMember) .contains(annotatedMember.getDescription() + " has annotation member of type <" + memberType.getName() + ">"); } + @Test + public void Dependency_from_type_parameter() { + @SuppressWarnings("unused") + class ClassWithTypeParameters { + } + + JavaClass javaClass = importClassesWithContext(ClassWithTypeParameters.class, String.class).get(ClassWithTypeParameters.class); + JavaTypeVariable typeParameter = javaClass.getTypeParameters().get(0); + + Dependency dependency = getOnlyElement(Dependency.tryCreateFromTypeParameter(typeParameter, typeParameter.getUpperBounds().get(0).toErasure())); + + assertThatType(dependency.getOriginClass()).matches(ClassWithTypeParameters.class); + assertThatType(dependency.getTargetClass()).matches(String.class); + assertThat(dependency.getDescription()).as("description").contains(String.format( + "Class <%s> has type parameter '%s' depending on <%s> in (%s.java:0)", + ClassWithTypeParameters.class.getName(), typeParameter.getName(), String.class.getName(), getClass().getSimpleName())); + } + @Test public void origin_predicates_match() { assertThatDependency(Origin.class, Target.class) diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java index c23d5e9218..bfb0c160df 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaClassTest.java @@ -1,5 +1,7 @@ package com.tngtech.archunit.core.domain; +import java.io.BufferedInputStream; +import java.io.File; import java.io.Serializable; import java.lang.annotation.Retention; import java.util.AbstractList; @@ -7,6 +9,7 @@ import java.util.Collection; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import com.google.common.collect.FluentIterable; @@ -574,6 +577,59 @@ public void finds_array_component_types_as_dependencies_from_self() { .inLocation(ArrayComponentTypeDependencies.class, 18)); } + @Test + public void direct_dependencies_from_self_by_type_parameter() { + @SuppressWarnings("unused") + class ClassWithTypeParameters< + FIRST extends List & Serializable & Comparable, + SECOND extends Map< + Map.Entry>, + Map>>>>>>>, + SELF extends ClassWithTypeParameters> { + } + + JavaClass javaClass = importClasses(ClassWithTypeParameters.class).get(ClassWithTypeParameters.class); + + assertThatDependencies(javaClass.getDirectDependenciesFromSelf()) + .contain(from(ClassWithTypeParameters.class).to(List.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'FIRST' depending on") + + .from(ClassWithTypeParameters.class).to(Serializable.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'FIRST' depending on") + + .from(ClassWithTypeParameters.class).to(Comparable.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'FIRST' depending on") + + .from(ClassWithTypeParameters.class).to(Map.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'SECOND' depending on") + + .from(ClassWithTypeParameters.class).to(Map.Entry.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'SECOND' depending on") + + .from(ClassWithTypeParameters.class).to(String.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'SECOND' depending on") + + .from(ClassWithTypeParameters.class).to(BufferedInputStream[][].class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'SECOND' depending on") + + .from(ClassWithTypeParameters.class).to(Serializable.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'SECOND' depending on") + + .from(ClassWithTypeParameters.class).to(List.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'SECOND' depending on") + + .from(ClassWithTypeParameters.class).to(Set.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'SECOND' depending on") + + .from(ClassWithTypeParameters.class).to(Iterable.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'SECOND' depending on") + + .from(ClassWithTypeParameters.class).to(File.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'SECOND' depending on") + ); + } + @Test public void direct_dependencies_from_self_finds_correct_set_of_target_types() { JavaClass javaClass = importPackagesOf(getClass()).get(ClassWithAnnotationDependencies.class); @@ -743,6 +799,34 @@ public void finds_array_component_types_as_dependencies_to_self() { .inLocation(ArrayComponentTypeDependencies.class, 18)); } + @Test + public void direct_dependencies_to_self_by_type_parameter() { + class ClassOtherTypeSignaturesDependOn { + } + @SuppressWarnings("unused") + class FirstDependingOnOtherThroughTypeParameter { + } + @SuppressWarnings("unused") + class SecondDependingOnOtherThroughTypeParameter< + U extends Map>>, + V extends Map> { + } + + JavaClass someClass = importClasses(ClassOtherTypeSignaturesDependOn.class, FirstDependingOnOtherThroughTypeParameter.class, SecondDependingOnOtherThroughTypeParameter.class) + .get(ClassOtherTypeSignaturesDependOn.class); + + assertThatDependencies(someClass.getDirectDependenciesToSelf()) + .contain(from(FirstDependingOnOtherThroughTypeParameter.class).to(ClassOtherTypeSignaturesDependOn.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'T' depending on") + + .from(SecondDependingOnOtherThroughTypeParameter.class).to(ClassOtherTypeSignaturesDependOn.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'U' depending on") + + .from(SecondDependingOnOtherThroughTypeParameter.class).to(ClassOtherTypeSignaturesDependOn.class).inLocation(getClass(), 0) + .withDescriptionContaining("type parameter 'V' depending on") + ); + } + @Test public void direct_dependencies_to_self_finds_correct_set_of_origin_types() { JavaClasses classes = importPackagesOf(getClass()); diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java index 4ef9d2eaef..1ce12894cc 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaTypeVariableTest.java @@ -19,18 +19,31 @@ public void type_variable_name() { class ClassWithUnboundTypeParameter { } - JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithUnboundTypeParameter.class).getTypeParameters().get(0); + JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithUnboundTypeParameter.class).getTypeParameters().get(0); assertThat(type.getName()).isEqualTo("SOME_NAME"); } + @Test + public void type_variable_class_owner() { + @SuppressWarnings("unused") + class ClassWithUnboundTypeParameter { + } + + JavaClass javaClass = new ClassFileImporter().importClass(ClassWithUnboundTypeParameter.class); + JavaTypeVariable type = javaClass.getTypeParameters().get(0); + + assertThat(type.getOwner()).isSameAs(javaClass); + assertThat(type.getGenericDeclaration()).isSameAs(javaClass); + } + @Test public void type_variable_upper_bounds() { @SuppressWarnings("unused") class ClassWithUnboundTypeParameter & Serializable> { } - JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithUnboundTypeParameter.class).getTypeParameters().get(0); + JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithUnboundTypeParameter.class).getTypeParameters().get(0); assertThatTypes(type.getBounds()).matchExactly(HashMap.class, Serializable.class); assertThatTypes(type.getUpperBounds()).matchExactly(HashMap.class, Serializable.class); @@ -42,7 +55,7 @@ public void erased_unbound_type_variable_is_java_lang_Object() { class ClassWithUnboundTypeParameter { } - JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithUnboundTypeParameter.class).getTypeParameters().get(0); + JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithUnboundTypeParameter.class).getTypeParameters().get(0); assertThatType(type.toErasure()).matches(Object.class); } @@ -53,7 +66,7 @@ public void erased_type_variable_bound_by_single_class_is_this_class() { class ClassWithBoundTypeParameterWithSingleClassBound { } - JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithBoundTypeParameterWithSingleClassBound.class).getTypeParameters().get(0); + JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithBoundTypeParameterWithSingleClassBound.class).getTypeParameters().get(0); assertThatType(type.toErasure()).matches(Serializable.class); } @@ -64,7 +77,7 @@ public void erased_type_variable_bound_by_single_generic_class_is_the_erasure_of class ClassWithBoundTypeParameterWithSingleGenericClassBound> { } - JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithBoundTypeParameterWithSingleGenericClassBound.class).getTypeParameters().get(0); + JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithBoundTypeParameterWithSingleGenericClassBound.class).getTypeParameters().get(0); assertThatType(type.toErasure()).matches(List.class); } @@ -75,7 +88,7 @@ public void erased_type_variable_bound_by_multiple_generic_classes_and_interface class ClassWithBoundTypeParameterWithMultipleGenericClassAndInterfaceBounds & Iterable & Serializable> { } - JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithBoundTypeParameterWithMultipleGenericClassAndInterfaceBounds.class).getTypeParameters().get(0); + JavaTypeVariable type = new ClassFileImporter().importClass(ClassWithBoundTypeParameterWithMultipleGenericClassAndInterfaceBounds.class).getTypeParameters().get(0); assertThatType(type.toErasure()).matches(HashMap.class); } @@ -86,7 +99,7 @@ public void erased_type_variable_bound_by_concrete_array_type_is_array_type() { class ClassWithBoundTypeParameterWithSingleGenericArrayBound, U extends List, V extends List[][][]>> { } - List typeParameters = new ClassFileImporter().importClass(ClassWithBoundTypeParameterWithSingleGenericArrayBound.class).getTypeParameters(); + List> typeParameters = new ClassFileImporter().importClass(ClassWithBoundTypeParameterWithSingleGenericArrayBound.class).getTypeParameters(); assertThatType(getTypeArgumentOfFirstBound(typeParameters.get(0)).toErasure()).matches(Object[].class); assertThatType(getTypeArgumentOfFirstBound(typeParameters.get(1)).toErasure()).matches(String[][].class); @@ -99,7 +112,7 @@ public void erased_type_variable_bound_by_generic_array_type_is_array_with_erasu class ClassWithBoundTypeParameterWithGenericArrayBounds, T extends List, U extends List, V extends List> { } - List typeParameters = new ClassFileImporter().importClass(ClassWithBoundTypeParameterWithGenericArrayBounds.class).getTypeParameters(); + List> typeParameters = new ClassFileImporter().importClass(ClassWithBoundTypeParameterWithGenericArrayBounds.class).getTypeParameters(); assertThatType(getTypeArgumentOfFirstBound(typeParameters.get(3)).toErasure()).matches(Object[].class); assertThatType(getTypeArgumentOfFirstBound(typeParameters.get(4)).toErasure()).matches(String[][].class); @@ -112,7 +125,7 @@ public void toString_unbounded() { class Unbounded { } - JavaTypeVariable typeVariable = new ClassFileImporter().importClass(Unbounded.class).getTypeParameters().get(0); + JavaTypeVariable typeVariable = new ClassFileImporter().importClass(Unbounded.class).getTypeParameters().get(0); assertThat(typeVariable.toString()) .contains(JavaTypeVariable.class.getSimpleName()) @@ -126,7 +139,7 @@ public void toString_upper_bounded_by_single_bound() { class BoundedBySingleBound { } - JavaTypeVariable typeVariable = new ClassFileImporter().importClass(BoundedBySingleBound.class).getTypeParameters().get(0); + JavaTypeVariable typeVariable = new ClassFileImporter().importClass(BoundedBySingleBound.class).getTypeParameters().get(0); assertThat(typeVariable.toString()) .contains(JavaTypeVariable.class.getSimpleName()) @@ -139,14 +152,14 @@ public void toString_upper_bounded_by_multiple_bounds() { class BoundedByMultipleBounds { } - JavaTypeVariable typeVariable = new ClassFileImporter().importClass(BoundedByMultipleBounds.class).getTypeParameters().get(0); + JavaTypeVariable typeVariable = new ClassFileImporter().importClass(BoundedByMultipleBounds.class).getTypeParameters().get(0); assertThat(typeVariable.toString()) .contains(JavaTypeVariable.class.getSimpleName()) .contains("NAME extends java.lang.String & java.io.Serializable"); } - private static JavaType getTypeArgumentOfFirstBound(JavaTypeVariable typeParameter) { + private static JavaType getTypeArgumentOfFirstBound(JavaTypeVariable typeParameter) { JavaParameterizedType firstBound = (JavaParameterizedType) typeParameter.getBounds().get(0); return firstBound.getActualTypeArguments().get(0); } diff --git a/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java b/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java index 098d9bdbcb..09a535b671 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/importer/ImportTestUtils.java @@ -342,7 +342,7 @@ public Set createInterfaces(JavaClass owner) { } @Override - public List createTypeParameters(JavaClass owner) { + public List> createTypeParameters(JavaClass owner) { return Collections.emptyList(); } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java b/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java index 7254ca00d4..a27b573443 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/Assertions.java @@ -96,7 +96,7 @@ public static JavaTypeAssertion assertThatType(JavaType javaType) { return new JavaTypeAssertion(javaType); } - public static JavaTypeVariableAssertion assertThatTypeVariable(JavaTypeVariable typeVariable) { + public static JavaTypeVariableAssertion assertThatTypeVariable(JavaTypeVariable typeVariable) { return new JavaTypeVariableAssertion(typeVariable); } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/DependenciesAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/DependenciesAssertion.java index 1ee77dd3de..629e972830 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/DependenciesAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/DependenciesAssertion.java @@ -166,13 +166,10 @@ private static class ExpectedDependency { } boolean matches(Dependency dependency) { - if (!dependency.getOriginClass().isEquivalentTo(origin) || !dependency.getTargetClass().isEquivalentTo(target)) { - return false; - } - if (descriptionPattern.isPresent() && !descriptionPattern.get().matcher(dependency.getDescription()).matches()) { - return false; - } - return !locationPart.isPresent() || dependency.getDescription().endsWith(locationPart.get()); + return dependency.getOriginClass().isEquivalentTo(origin) + && dependency.getTargetClass().isEquivalentTo(target) + && (!descriptionPattern.isPresent() || descriptionPattern.get().matcher(dependency.getDescription()).matches()) + && (!locationPart.isPresent() || dependency.getDescription().endsWith(locationPart.get())); } public void descriptionContaining(String descriptionTemplate, Object[] args) { diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeAssertion.java index b4dd72810d..2bd4a81ec7 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeAssertion.java @@ -75,9 +75,9 @@ public JavaTypeAssertion hasTypeParameters(String... names) { @SuppressWarnings("OptionalGetWithoutIsPresent") // checked via AssertJ public JavaTypeVariableOfClassAssertion hasTypeParameter(String name) { - List typeVariables = actualClass().getTypeParameters(); + List> typeVariables = actualClass().getTypeParameters(); - Optional variable = FluentIterable.from(typeVariables).firstMatch(toGuava(name(name))); + Optional> variable = FluentIterable.from(typeVariables).firstMatch(toGuava(name(name))); assertThat(variable).as("Type variable with name '%s'", name).isPresent(); return new JavaTypeVariableOfClassAssertion(variable.get()); @@ -109,8 +109,8 @@ public static String getExpectedPackageName(Class clazz) { return getExpectedPackageName(clazz.getComponentType()); } - public class JavaTypeVariableOfClassAssertion extends AbstractObjectAssert { - private JavaTypeVariableOfClassAssertion(JavaTypeVariable actual) { + public class JavaTypeVariableOfClassAssertion extends AbstractObjectAssert> { + private JavaTypeVariableOfClassAssertion(JavaTypeVariable actual) { super(actual, JavaTypeVariableOfClassAssertion.class); } diff --git a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java index 9d0d146717..820227e0c4 100644 --- a/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java +++ b/archunit/src/test/java/com/tngtech/archunit/testutil/assertion/JavaTypeVariableAssertion.java @@ -23,8 +23,8 @@ import static com.tngtech.archunit.testutil.assertion.JavaTypeVariableAssertion.ExpectedConcreteWildcardType.wildcardType; import static java.util.Collections.emptyList; -public class JavaTypeVariableAssertion extends AbstractObjectAssert { - public JavaTypeVariableAssertion(JavaTypeVariable actual) { +public class JavaTypeVariableAssertion extends AbstractObjectAssert> { + public JavaTypeVariableAssertion(JavaTypeVariable actual) { super(actual, JavaTypeVariableAssertion.class); } @@ -215,7 +215,7 @@ public ExpectedConcreteTypeVariable withoutUpperBounds() { @Override public void assertMatchWith(JavaType actual, DescriptionContext context) { assertThat(actual).as(context.step("JavaType").toString()).isInstanceOf(JavaTypeVariable.class); - JavaTypeVariable actualTypeVariable = (JavaTypeVariable) actual; + JavaTypeVariable actualTypeVariable = (JavaTypeVariable) actual; assertThat(actualTypeVariable.getName()).as(context.step("type variable name").toString()).isEqualTo(name); if (upperBounds != null) {