Skip to content

Commit

Permalink
Add generic superclasses #503
Browse files Browse the repository at this point in the history
As a next step to fully support generics within ArchUnit this PR adds support for generic superclasses. In particular

* add `Optional<JavaType> JavaClass.getSuperclass()`, which will return a `JavaParameterizedType` for a parameterized superclass and the raw type otherwise
* add all type arguments of generic superclasses to `JavaClass.directDependencies{From/To}Self`

Example: ArchUnit would now detect `String` as a `Dependency` of `class Foo extends Base<List<? super String>>`

It contains a minor breaking change: Before it was possible to receive a `JavaParameterizedType` without any type arguments. I.e. for `class Foo<T extends String>` the upper bound of `T` would actually be a `JavaParameterizedType` of type `String` without any type arguments. This is inconsistent to the Java Reflection API (and doesn't really make sense), so I have changed this within the PR to return a `JavaClass` instead. If any users of ArchUnit `0.15.0` (where this API was introduced) would use this low-level API and assumed that every `JavaType` within a type signature is either `JavaTypeVariable`, `JavaWildcard` or `JavaParameterizedType`, then this code might break now. I consider the danger slim though, because most users are probably either just interested in the `Dependencies` caused by type parameter bounds (which haven't changed) or handle all possible subclasses of `JavaType`, which would then include `JavaClass` again.
  • Loading branch information
codecholeric authored Jan 30, 2021
2 parents 4db910f + 0f45abd commit 11006ed
Show file tree
Hide file tree
Showing 112 changed files with 1,836 additions and 893 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ private static DescribedPredicate<JavaClass> haveLocalBeanSubclass() {
return new DescribedPredicate<JavaClass>("have subclass that is a local bean") {
@Override
public boolean apply(JavaClass input) {
for (JavaClass subClass : input.getAllSubClasses()) {
if (isLocalBeanImplementation(subClass, input)) {
for (JavaClass subclass : input.getAllSubclasses()) {
if (isLocalBeanImplementation(subclass, input)) {
return true;
}
}
Expand All @@ -93,13 +93,13 @@ private static ArchCondition<JavaClass> haveAUniqueImplementation() {
@Override
public void check(JavaClass businessInterface, ConditionEvents events) {
events.add(new SimpleConditionEvent(businessInterface,
businessInterface.getAllSubClasses().size() <= 1,
businessInterface.getAllSubclasses().size() <= 1,
describe(businessInterface)));
}

private String describe(JavaClass businessInterface) {
return String.format("%s is implemented by %s",
businessInterface.getSimpleName(), joinNamesOf(businessInterface.getAllSubClasses()));
businessInterface.getSimpleName(), joinNamesOf(businessInterface.getAllSubclasses()));
}

private String joinNamesOf(Set<JavaClass> implementations) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ private static ArchCondition<JavaClass> notCreateProblematicClassesOutsideOfWork
target(is(constructor())).and(targetOwner(is(assignableTo(ThirdPartyClassWithProblem.class))));

DescribedPredicate<JavaCall<?>> notFromWithinThirdPartyClass =
originOwner(is(not(assignableTo(ThirdPartyClassWithProblem.class)))).forSubType();
originOwner(is(not(assignableTo(ThirdPartyClassWithProblem.class)))).forSubtype();

DescribedPredicate<JavaCall<?>> notFromWorkaroundFactory =
originOwner(is(not(equivalentTo(ThirdPartyClassWorkaroundFactory.class)))).forSubType();
originOwner(is(not(equivalentTo(ThirdPartyClassWorkaroundFactory.class)))).forSubtype();

DescribedPredicate<JavaCall<?>> targetIsIllegalConstructorOfThirdPartyClass =
constructorCallOfThirdPartyClass.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ Class <com.tngtech.archunit.example.layers.persistence.layerviolation.DaoCalling
Class <com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules> has annotation member of type <com.tngtech.archunit.example.layers.service.ServiceType> in (ServiceViolatingLayerRules.java:0)
Class <com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules> has annotation member of type <com.tngtech.archunit.example.layers.service.SimpleServiceAnnotation> in (ServiceViolatingLayerRules.java:0)
Class <com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules> is annotated with <com.tngtech.archunit.example.layers.service.ComplexServiceAnnotation> in (ServiceViolatingLayerRules.java:0)
Class <com.tngtech.archunit.example.layers.service.SpecialServiceHelper> extends class <com.tngtech.archunit.example.layers.service.ServiceHelper> in (SpecialServiceHelper.java:0)
Class <com.tngtech.archunit.example.layers.service.impl.ServiceImplementation> implements interface <com.tngtech.archunit.example.layers.service.ServiceInterface> in (ServiceImplementation.java:0)
Constructor <com.tngtech.archunit.example.layers.SomeMediator.<init>(com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules)> has parameter of type <com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules> in (SomeMediator.java:0)
Constructor <com.tngtech.archunit.example.layers.service.SpecialServiceHelper.<init>()> calls constructor <com.tngtech.archunit.example.layers.service.ServiceHelper.<init>()> in (SpecialServiceHelper.java:9)
Field <com.tngtech.archunit.example.layers.SomeMediator.service> has type <com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules> in (SomeMediator.java:0)
Field <com.tngtech.archunit.example.layers.controller.SomeController.otherService> has type <com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules> in (SomeController.java:0)
Field <com.tngtech.archunit.example.layers.controller.SomeController.service> has type <com.tngtech.archunit.example.layers.service.ServiceViolatingDaoRules> in (SomeController.java:0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ private static DescribedPredicate<JavaClass> haveLocalBeanSubclass() {
return new DescribedPredicate<JavaClass>("have subclass that is a local bean") {
@Override
public boolean apply(JavaClass input) {
for (JavaClass subClass : input.getAllSubClasses()) {
if (isLocalBeanImplementation(subClass, input)) {
for (JavaClass subclass : input.getAllSubclasses()) {
if (isLocalBeanImplementation(subclass, input)) {
return true;
}
}
Expand All @@ -90,13 +90,13 @@ private static ArchCondition<JavaClass> haveAUniqueImplementation() {
@Override
public void check(JavaClass businessInterface, ConditionEvents events) {
events.add(new SimpleConditionEvent(businessInterface,
businessInterface.getAllSubClasses().size() <= 1,
businessInterface.getAllSubclasses().size() <= 1,
describe(businessInterface)));
}

private String describe(JavaClass businessInterface) {
return String.format("%s is implemented by %s",
businessInterface.getSimpleName(), joinNamesOf(businessInterface.getAllSubClasses()));
businessInterface.getSimpleName(), joinNamesOf(businessInterface.getAllSubclasses()));
}

private String joinNamesOf(Set<JavaClass> implementations) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ private static ArchCondition<JavaClass> notCreateProblematicClassesOutsideOfWork
target(is(constructor())).and(targetOwner(is(assignableTo(ThirdPartyClassWithProblem.class))));

DescribedPredicate<JavaCall<?>> notFromWithinThirdPartyClass =
originOwner(is(not(assignableTo(ThirdPartyClassWithProblem.class)))).forSubType();
originOwner(is(not(assignableTo(ThirdPartyClassWithProblem.class)))).forSubtype();

DescribedPredicate<JavaCall<?>> notFromWorkaroundFactory =
originOwner(is(not(equivalentTo(ThirdPartyClassWorkaroundFactory.class)))).forSubType();
originOwner(is(not(equivalentTo(ThirdPartyClassWorkaroundFactory.class)))).forSubtype();

DescribedPredicate<JavaCall<?>> targetIsIllegalConstructorOfThirdPartyClass =
constructorCallOfThirdPartyClass.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ Class <com.tngtech.archunit.example.layers.persistence.layerviolation.DaoCalling
Class <com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules> has annotation member of type <com.tngtech.archunit.example.layers.service.ServiceType> in (ServiceViolatingLayerRules.java:0)
Class <com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules> has annotation member of type <com.tngtech.archunit.example.layers.service.SimpleServiceAnnotation> in (ServiceViolatingLayerRules.java:0)
Class <com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules> is annotated with <com.tngtech.archunit.example.layers.service.ComplexServiceAnnotation> in (ServiceViolatingLayerRules.java:0)
Class <com.tngtech.archunit.example.layers.service.SpecialServiceHelper> extends class <com.tngtech.archunit.example.layers.service.ServiceHelper> in (SpecialServiceHelper.java:0)
Class <com.tngtech.archunit.example.layers.service.impl.ServiceImplementation> implements interface <com.tngtech.archunit.example.layers.service.ServiceInterface> in (ServiceImplementation.java:0)
Constructor <com.tngtech.archunit.example.layers.SomeMediator.<init>(com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules)> has parameter of type <com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules> in (SomeMediator.java:0)
Constructor <com.tngtech.archunit.example.layers.service.SpecialServiceHelper.<init>()> calls constructor <com.tngtech.archunit.example.layers.service.ServiceHelper.<init>()> in (SpecialServiceHelper.java:9)
Field <com.tngtech.archunit.example.layers.SomeMediator.service> has type <com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules> in (SomeMediator.java:0)
Field <com.tngtech.archunit.example.layers.controller.SomeController.otherService> has type <com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules> in (SomeController.java:0)
Field <com.tngtech.archunit.example.layers.controller.SomeController.service> has type <com.tngtech.archunit.example.layers.service.ServiceViolatingDaoRules> in (SomeController.java:0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import com.tngtech.archunit.example.layers.thirdparty.ThirdPartyClassWithProblem;
import com.tngtech.archunit.example.layers.thirdparty.ThirdPartyClassWorkaroundFactory;
import com.tngtech.archunit.example.layers.thirdparty.ThirdPartySubClassWithProblem;
import com.tngtech.archunit.example.layers.thirdparty.ThirdPartySubclassWithProblem;

public class ClassViolatingThirdPartyRules {
ThirdPartyClassWithProblem illegallyInstantiateThirdPartyClass() {
Expand All @@ -13,11 +13,11 @@ ThirdPartyClassWithProblem correctlyInstantiateThirdPartyClass() {
return new ThirdPartyClassWorkaroundFactory().create();
}

ThirdPartySubClassWithProblem illegallyInstantiateThirdPartySubClass() {
return new ThirdPartySubClassWithProblem();
ThirdPartySubclassWithProblem illegallyInstantiateThirdPartySubclass() {
return new ThirdPartySubclassWithProblem();
}

ThirdPartySubClassWithProblem correctlyInstantiateThirdPartySubClass() {
return new ThirdPartyClassWorkaroundFactory().createSubClass();
ThirdPartySubclassWithProblem correctlyInstantiateThirdPartySubclass() {
return new ThirdPartyClassWorkaroundFactory().createSubclass();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.tngtech.archunit.example.layers.service;

import java.util.HashMap;
import java.util.Set;

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

public class SpecialServiceHelper extends ServiceHelper<SomeUtility, HashMap<?, Set<? super SomeEnum>>> {
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ public ThirdPartyClassWithProblem create() {
return new ThirdPartyClassWithProblem();
}

public ThirdPartySubClassWithProblem createSubClass() {
public ThirdPartySubclassWithProblem createSubclass() {
// some workaround here
return new ThirdPartySubClassWithProblem();
return new ThirdPartySubclassWithProblem();
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.tngtech.archunit.example.layers.thirdparty;

public class ThirdPartySubClassWithProblem extends ThirdPartyClassWithProblem {
public class ThirdPartySubclassWithProblem extends ThirdPartyClassWithProblem {
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ private static DescribedPredicate<JavaAccess<?>> originNeitherConstructorNorPost
new DescribedPredicate<JavaClass>("have subclass that is a local bean") {
@Override
public boolean apply(JavaClass input) {
for (JavaClass subClass : input.getAllSubClasses()) {
if (isLocalBeanImplementation(subClass, input)) {
for (JavaClass subclass : input.getAllSubclasses()) {
if (isLocalBeanImplementation(subclass, input)) {
return true;
}
}
Expand All @@ -90,13 +90,13 @@ private boolean isLocalBeanImplementation(JavaClass bean, JavaClass businessInte
@Override
public void check(JavaClass businessInterface, ConditionEvents events) {
events.add(new SimpleConditionEvent(businessInterface,
businessInterface.getAllSubClasses().size() <= 1,
businessInterface.getAllSubclasses().size() <= 1,
describe(businessInterface)));
}

private String describe(JavaClass businessInterface) {
return String.format("%s is implemented by %s",
businessInterface.getSimpleName(), joinNamesOf(businessInterface.getAllSubClasses()));
businessInterface.getSimpleName(), joinNamesOf(businessInterface.getAllSubclasses()));
}

private String joinNamesOf(Set<JavaClass> implementations) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,10 @@ private ArchCondition<JavaClass> notCreateProblematicClassesOutsideOfWorkaroundF
target(is(constructor())).and(targetOwner(is(assignableTo(ThirdPartyClassWithProblem.class))));

DescribedPredicate<JavaCall<?>> notFromWithinThirdPartyClass =
originOwner(is(not(assignableTo(ThirdPartyClassWithProblem.class)))).forSubType();
originOwner(is(not(assignableTo(ThirdPartyClassWithProblem.class)))).forSubtype();

DescribedPredicate<JavaCall<?>> notFromWorkaroundFactory =
originOwner(is(not(equivalentTo(ThirdPartyClassWorkaroundFactory.class)))).forSubType();
originOwner(is(not(equivalentTo(ThirdPartyClassWorkaroundFactory.class)))).forSubtype();

DescribedPredicate<JavaCall<?>> targetIsIllegalConstructorOfThirdPartyClass =
constructorCallOfThirdPartyClass.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ Class <com.tngtech.archunit.example.layers.persistence.layerviolation.DaoCalling
Class <com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules> has annotation member of type <com.tngtech.archunit.example.layers.service.ServiceType> in (ServiceViolatingLayerRules.java:0)
Class <com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules> has annotation member of type <com.tngtech.archunit.example.layers.service.SimpleServiceAnnotation> in (ServiceViolatingLayerRules.java:0)
Class <com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules> is annotated with <com.tngtech.archunit.example.layers.service.ComplexServiceAnnotation> in (ServiceViolatingLayerRules.java:0)
Class <com.tngtech.archunit.example.layers.service.SpecialServiceHelper> extends class <com.tngtech.archunit.example.layers.service.ServiceHelper> in (SpecialServiceHelper.java:0)
Class <com.tngtech.archunit.example.layers.service.impl.ServiceImplementation> implements interface <com.tngtech.archunit.example.layers.service.ServiceInterface> in (ServiceImplementation.java:0)
Constructor <com.tngtech.archunit.example.layers.SomeMediator.<init>(com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules)> has parameter of type <com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules> in (SomeMediator.java:0)
Constructor <com.tngtech.archunit.example.layers.service.SpecialServiceHelper.<init>()> calls constructor <com.tngtech.archunit.example.layers.service.ServiceHelper.<init>()> in (SpecialServiceHelper.java:9)
Field <com.tngtech.archunit.example.layers.SomeMediator.service> has type <com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules> in (SomeMediator.java:0)
Field <com.tngtech.archunit.example.layers.controller.SomeController.otherService> has type <com.tngtech.archunit.example.layers.service.ServiceViolatingLayerRules> in (SomeController.java:0)
Field <com.tngtech.archunit.example.layers.controller.SomeController.service> has type <com.tngtech.archunit.example.layers.service.ServiceViolatingDaoRules> in (SomeController.java:0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ public class ArchUnitArchitectureTest {
private static DescribedPredicate<JavaCall<?>> typeIsIllegallyResolvedViaReflection() {
DescribedPredicate<JavaCall<?>> explicitlyAllowedUsage =
origin(is(annotatedWith(MayResolveTypesViaReflection.class)))
.or(contextIsAnnotatedWith(MayResolveTypesViaReflection.class)).forSubType();
.or(contextIsAnnotatedWith(MayResolveTypesViaReflection.class)).forSubtype();

return classIsResolvedViaReflection().and(not(explicitlyAllowedUsage));
}
Expand Down Expand Up @@ -100,7 +100,7 @@ private static DescribedPredicate<JavaCall<?>> classIsResolvedViaReflection() {
target(HasOwner.Functions.Get.<JavaClass>owner()
.is(equivalentTo(Class.class)))
.and(target(has(name("forName"))))
.forSubType();
.forSubtype();
DescribedPredicate<JavaCall<?>> targetIsMarked =
annotatedWith(ResolvesTypesViaReflection.class).onResultOf(Get.target());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,13 @@ public class PublicAPIRules {
.andShould().haveRawReturnType(not(guavaClass()).as("that are no Guava types")));

private static DescribedPredicate<JavaClass> publicAPI() {
return annotatedWith(PublicAPI.class).<JavaClass>forSubType()
return annotatedWith(PublicAPI.class).<JavaClass>forSubtype()
.or(haveMemberThatBelongsToPublicApi())
.or(markedAsPublicAPIForInheritance());
}

private static DescribedPredicate<JavaClass> internal() {
return annotatedWith(Internal.class).<JavaClass>forSubType()
return annotatedWith(Internal.class).<JavaClass>forSubtype()
.or(equivalentTo(Internal.class));
}

Expand Down Expand Up @@ -215,8 +215,8 @@ public boolean apply(JavaClass input) {
}
}
return input.getConstructors().isEmpty() &&
input.getSuperClass().isPresent() &&
haveAPublicConstructor().apply(input.getSuperClass().get());
input.getRawSuperclass().isPresent() &&
haveAPublicConstructor().apply(input.getRawSuperclass().get());
}
};
}
Expand All @@ -236,8 +236,8 @@ public boolean apply(JavaClass input) {
}

private static DescribedPredicate<JavaMember> withoutAPIMarking() {
return not(annotatedWith(PublicAPI.class)).<JavaMember>forSubType()
.and(not(annotatedWith(Internal.class)).forSubType())
return not(annotatedWith(PublicAPI.class)).<JavaMember>forSubtype()
.and(not(annotatedWith(Internal.class)).forSubtype())
.and(declaredIn(modifier(PUBLIC)))
.as("without API marking");
}
Expand Down
Loading

0 comments on commit 11006ed

Please sign in to comment.