Search code examples
archunit

ArchUnit Rule to prevent access to derived classes?


A colleague did some refactoring and moved a method to a superclass to be able to reuse it from an other child-class, too. The IDE handled that quickly and without any complains. That method, however, internally referred to a constant which was not moved with the method, i.e. the parent class thus now referred to a constant in one of its child-classes which of course is a no-no!

How can I phrase an ArchUnit rule that prevents such references from parent to members/methods/local classes and enums/etc. in child-classes (or in general: anything in classes further down the hierarchy)?


Solution

  • For direct dependencies, you can use the following custom ArchCondition:

    @ArchTest
    ArchRule rule = noClasses().should(new ArchCondition<JavaClass>("depend on their children") {
        @Override
        public void check(JavaClass parentClass, ConditionEvents events) {
            parentClass.getDirectDependenciesFromSelf().stream()
                .filter(dependency -> dependency.getTargetClass() != parentClass
                        && dependency.getTargetClass().isAssignableTo(parentClass.getName()))
                .forEach(dependency -> events.add(satisfied(dependency, dependency.getDescription())));
        }
    });
    

    It can easily be adapted to also catch transitive dependencies such as in this example:

    class Parent { 
        Friend friend; 
    }
    class Friend { 
        Child child; 
    }
    class Child extends Parent { 
    }
    

    You can basically replace getDirectDependenciesFromSelf with getTransitiveDependenciesFromSelf:

    @ArchTest
    ArchRule rule = noClasses().should(new ArchCondition<JavaClass>("depend on their children") {
        @Override
        public void check(JavaClass parentClass, ConditionEvents events) {
            parentClass.getTransitiveDependenciesFromSelf().stream()
                .filter(dependency -> dependency.getTargetClass() != parentClass
                        && dependency.getTargetClass().isAssignableTo(parentClass.getName()))
                .forEach(dependency -> events.add(satisfied(dependency, parentClass.getName()
                        + " (transitively) depends on its child: " + dependency.getDescription())));
        }
    });
    

    FYI: I'm using the following static imports:

    import static com.tngtech.archunit.lang.SimpleConditionEvent.satisfied;
    import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;