All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tngtech.archunit.lang.ArchRule Maven / Gradle / Ivy

Go to download

A Java architecture test library, to specify and assert architecture rules in plain Java - Module 'archunit'

The newest version!
/*
 * Copyright 2014-2024 TNG Technology Consulting GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.tngtech.archunit.lang;

import java.util.Collection;
import java.util.Optional;

import com.google.common.collect.ImmutableList;
import com.tngtech.archunit.Internal;
import com.tngtech.archunit.PublicAPI;
import com.tngtech.archunit.base.DescribedIterable;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.domain.properties.CanOverrideDescription;
import com.tngtech.archunit.lang.extension.ArchUnitExtensions;
import com.tngtech.archunit.lang.extension.EvaluatedRule;
import com.tngtech.archunit.lang.syntax.ArchRuleDefinition;
import com.tngtech.archunit.lang.syntax.elements.ClassesShould;
import com.tngtech.archunit.lang.syntax.elements.ClassesThat;
import com.tngtech.archunit.lang.syntax.elements.GivenClasses;

import static com.google.common.collect.Iterables.isEmpty;
import static com.tngtech.archunit.PublicAPI.Usage.ACCESS;

/**
 * Represents a rule about a specified set of objects of interest (e.g. {@link JavaClass}).
 * To define a rule, use one of the factory methods within {@link ArchRuleDefinition}, for example
 * 


 * ArchRule rule = {@link ArchRuleDefinition#noClasses()}.{@link GivenClasses#that() that()}.{@link ClassesThat#resideInAPackage(String) resideInAPackage("..svc..")}
 *                     .{@link GivenClasses#should() should()}.{@link ClassesShould#accessClassesThat() accessClassesThat()}.{@link ClassesThat#resideInAPackage(String) resideInAPackage("..ui..")};
 * rule.check(importedJavaClasses);
 * 
*
* To write rules on custom objects, you can use {@link ArchRuleDefinition#all(ClassesTransformer)}, where * {@link ClassesTransformer} defines how the type of objects can be created from imported {@link JavaClasses}. * E.g. if you want to define a rule on all imported methods you could specify a transformer to retrieve methods * from classes, or if you're interested in slices of packages, the input transformer would specify how to transform * the imported classes to those slices to run an {@link ArchCondition} against. * * @see com.tngtech.archunit.library.dependencies.Slices.Transformer */ @PublicAPI(usage = ACCESS) public interface ArchRule extends CanBeEvaluated, CanOverrideDescription { @PublicAPI(usage = ACCESS) void check(JavaClasses classes); @PublicAPI(usage = ACCESS) ArchRule because(String reason); /** * If set to {@code true} allows the should-clause of this rule to be checked against an empty set of elements. * Otherwise, the rule will fail with a respective message. This is to prevent possible implementation errors, * like filtering for a non-existing package in the that-clause causing an always-passing rule.
* Note that this method will override the configuration property {@code archRule.failOnEmptyShould}. * * @param allowEmptyShould Whether the rule fails if the should-clause is evaluated with an empty set of elements * @return A (new) {@link ArchRule} with adjusted {@code allowEmptyShould} behavior */ @PublicAPI(usage = ACCESS) ArchRule allowEmptyShould(boolean allowEmptyShould); @PublicAPI(usage = ACCESS) final class Assertions { private static final ArchUnitExtensions extensions = new ArchUnitExtensions(); private Assertions() { } @PublicAPI(usage = ACCESS) public static void check(ArchRule rule, JavaClasses classes) { EvaluationResult result = rule.evaluate(classes); extensions.dispatch(new SimpleEvaluatedRule(rule, classes, result)); assertNoViolation(result); } @PublicAPI(usage = ACCESS) public static void assertNoViolation(EvaluationResult result) { FailureReport report = result.getFailureReport(); if (!report.isEmpty()) { throw new AssertionError(report.toString()); } } private static class SimpleEvaluatedRule implements EvaluatedRule { private final ArchRule rule; private final JavaClasses importedClasses; private final EvaluationResult evaluationResult; SimpleEvaluatedRule(ArchRule rule, JavaClasses importedClasses, EvaluationResult evaluationResult) { this.rule = rule; this.importedClasses = importedClasses; this.evaluationResult = evaluationResult; } @Override public ArchRule getRule() { return rule; } @Override public JavaClasses getClasses() { return importedClasses; } @Override public EvaluationResult getResult() { return evaluationResult; } } } @Internal class Factory { public static ArchRule create(ClassesTransformer classesTransformer, ArchCondition condition, Priority priority) { return new SimpleArchRule<>(priority, classesTransformer, condition, Optional.empty(), AllowEmptyShould.AS_CONFIGURED); } public static ArchRule withBecause(ArchRule rule, String reason) { return rule.as(createBecauseDescription(rule, reason)); } static String createBecauseDescription(ArchRule rule, String reason) { return rule.getDescription() + ", because " + reason; } private static class SimpleArchRule implements ArchRule { private static final String FAIL_ON_EMPTY_SHOULD_PROPERTY_NAME = "archRule.failOnEmptyShould"; private final Priority priority; private final ClassesTransformer classesTransformer; private final ArchCondition condition; private final Optional overriddenDescription; private final AllowEmptyShould allowEmptyShould; private SimpleArchRule(Priority priority, ClassesTransformer classesTransformer, ArchCondition condition, Optional overriddenDescription, AllowEmptyShould allowEmptyShould) { this.priority = priority; this.classesTransformer = classesTransformer; this.condition = condition; this.overriddenDescription = overriddenDescription; this.allowEmptyShould = allowEmptyShould; } @Override public ArchRule as(String newDescription) { return new SimpleArchRule<>(priority, classesTransformer, condition, Optional.of(newDescription), allowEmptyShould); } @Override public void check(JavaClasses classes) { Assertions.check(this, classes); } @Override public ArchRule because(String reason) { return withBecause(this, reason); } @Override public ArchRule allowEmptyShould(boolean allowEmptyShould) { return new SimpleArchRule<>(priority, classesTransformer, condition, overriddenDescription, AllowEmptyShould.fromBoolean(allowEmptyShould)); } @Override public EvaluationResult evaluate(JavaClasses classes) { Collection allObjects = toCollection(classesTransformer.transform(classes)); verifyNoEmptyShouldIfEnabled(allObjects); condition.init(allObjects); ConditionEvents events = ConditionEvents.Factory.create(); for (T object : allObjects) { condition.check(object, events); } condition.finish(events); return new EvaluationResult(this, events, priority); } @SuppressWarnings("unchecked") private Collection toCollection(DescribedIterable iterable) { return iterable instanceof Collection ? (Collection) iterable : ImmutableList.copyOf(iterable); } private void verifyNoEmptyShouldIfEnabled(Iterable allObjects) { if (isEmpty(allObjects) && !allowEmptyShould.isAllowed()) { throw new AssertionError(String.format( "Rule '%s' failed to check any classes. " + "This means either that no classes have been passed to the rule at all, " + "or that no classes passed to the rule matched the `that()` clause. " + "To allow rules being evaluated without checking any classes you can either " + "use `%s.allowEmptyShould(true)` on a single rule or set the configuration property `%s = false` " + "to change the behavior globally.", getDescription(), ArchRule.class.getSimpleName(), FAIL_ON_EMPTY_SHOULD_PROPERTY_NAME)); } } @Override public String getDescription() { return overriddenDescription.orElseGet(() -> classesTransformer.getDescription() + " should " + condition.getDescription()); } @Override public String toString() { return getDescription(); } } } @Internal interface Transformation { ArchRule apply(ArchRule rule); @Internal final class As implements Transformation { private final String description; public As(String description) { this.description = description; } @Override public ArchRule apply(ArchRule rule) { return rule.as(description); } @Override public String toString() { return String.format("as '%s'", description); } } @Internal final class Because implements Transformation { private final String reason; public Because(String reason) { this.reason = reason; } @Override public ArchRule apply(ArchRule rule) { return rule.because(reason); } @Override public String toString() { return String.format("because '%s'", reason); } } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy