Mahatma_Fatal_Error
Mahatma_Fatal_Error

Reputation: 779

How to prevent generic usage of a certain API (e.g Streaming API or Builder API) with ArchUnit?

Given a set of classes where it should not be forbidden to use certain APIs (for whatever reason).

For example, prohibiting Java 8 Streaming API, or calling a Builder inner class because you want to force the usage of loops and constructors for particular classes.

The critical part is that you do not know the ownerName beforehand and there is no callMethod(methodName) where I could coarse-grainy disallow calling stream() or build() methods.

Any ideas?

Upvotes: 0

Views: 315

Answers (1)

Manfred
Manfred

Reputation: 3142

You can compose powerful ArchRules using predicates instead of the fluent API; in your case with .callMethodWhere(DescribedPredicate<? super JavaMethodCall> predicate).

To prevent the usage of someCollection.stream(), you could start with HasName.Predicates.name:

noClasses().should().callMethodWhere(name("stream"))

To avoid false-positives, the rule should probably become more specific.

ArchUnit ships lots of useful pre-defined predicates; you just need to find them (and possibly deal with their covariant parameter types via DescribedPredicate.forSubtype):

noClasses().should().callMethodWhere(target(
    name("stream").<CodeUnitCallTarget>forSubtype().and(
        owner(assignableTo(Collection.class)).<CodeUnitCallTarget>forSubtype().and(
                rawParameterTypes(new Class[0])
        )
    )
))

I personally find it simpler to define a custom predicate instead (e.g. using DescribedPredicate.describe):

noClasses().should().callMethodWhere(target(describe("is Collection.stream()",
    target -> "stream".equals(target.getName()) &&
            target.getOwner().isAssignableTo(Collection.class) &&
            target.getParameterTypes().isEmpty()
)))

FYI: My solution uses the following imports:

import static com.tngtech.archunit.base.DescribedPredicate.describe;
import static com.tngtech.archunit.core.domain.JavaCall.Predicates.target;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.assignableTo;
import static com.tngtech.archunit.core.domain.properties.HasName.Predicates.name;
import static com.tngtech.archunit.core.domain.properties.HasOwner.Predicates.With.owner;
import static com.tngtech.archunit.core.domain.properties.HasParameterTypes.Predicates.rawParameterTypes;
import static com.tngtech.archunit.lang.syntax.ArchRuleDefinition.noClasses;

Upvotes: 2

Related Questions