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

de.spricom.dessert.assertions.SliceAssert Maven / Gradle / Ivy

package de.spricom.dessert.assertions;

/*-
 * #%L
 * Dessert Dependency Assertion Library for Java
 * %%
 * Copyright (C) 2017 - 2023 Hans Jörg Heßmann
 * %%
 * 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.
 * #L%
 */

import de.spricom.dessert.slicing.Clazz;
import de.spricom.dessert.slicing.ConcreteSlice;
import de.spricom.dessert.slicing.Slice;
import de.spricom.dessert.slicing.Slices;
import de.spricom.dessert.util.Dag;

import java.util.*;

/**
 * Implements a DSL for slice assertions using a fluent API.
 */
public class SliceAssert {
    private final Iterable slices;
    private final Slice union;
    private IllegalDependenciesRenderer violationsRenderer = new DefaultIllegalDependenciesRenderer();
    private CycleRenderer cycleRenderer = new DefaultCycleRenderer();

    SliceAssert(Iterable slices) {
        this.slices = slices;
        this.union = Slices.of(slices);
    }

    /**
     * Use custom renderer to produces the {@link AssertionError} message
     * for dependency violations.
     *
     * @param renderer the renderer
     * @return this instance (fluent API)
     */
    public SliceAssert renderWith(IllegalDependenciesRenderer renderer) {
        this.violationsRenderer = renderer;
        return this;
    }

    /**
     * Use custom renderer to produces the {@link AssertionError} message
     * for a detected cycle.
     *
     * @param renderer the renderer
     * @return this instance (fluent API)
     */
    public SliceAssert renderCycleWith(CycleRenderer renderer) {
        this.cycleRenderer = renderer;
        return this;
    }

    /**
     * Assert the current slices have no other dependencies than those contained by the slices
     * passed to this method.
     *
     * @param others the slices to check dependencies for
     * @return this instance (fluent API)
     */
    public SliceAssert usesOnly(Iterable others) {
        IllegalDependencies illegalDependencies = new IllegalDependencies();
        for (Clazz entry : union.getClazzes()) {
            for (Clazz dependency : entry.getDependencies().getClazzes()) {
                if (!union.contains(dependency) && !containsAny(others, dependency)) {
                    illegalDependencies.add(entry, dependency);
                }
            }
        }
        if (!illegalDependencies.isEmpty()) {
            throw new AssertionError(violationsRenderer.render(illegalDependencies));
        }
        return this;
    }

    /**
     * Plural alias for {@link #usesOnly(Iterable)}.
     *
     * @param others the slices to check dependencies for
     * @return this instance (fluent API)
     */
    public SliceAssert useOnly(Iterable others) {
        return usesOnly(others);
    }

    /**
     * Alternative for {@link #usesOnly(Iterable)}.
     *
     * @param others the slices to check dependencies for
     * @return this instance (fluent API)
     * @see #usesOnly(Iterable)
     */
    public SliceAssert usesOnly(Slice... others) {
        return usesOnly(Arrays.asList(others));
    }

    /**
     * Plural alias for {@link #usesOnly(Iterable)}.
     *
     * @param others the slices to check dependencies for
     * @return this instance (fluent API)
     */
    public SliceAssert useOnly(Slice... others) {
        return usesOnly(others);
    }

    /**
     * Assert the current slices have no dependency to any class contained by the slices
     * passed to this method.
     *
     * @param others the slices to check dependencies for
     * @return this instance (fluent API)
     */
    public SliceAssert usesNot(Iterable others) {
        IllegalDependencies illegalDependencies = new IllegalDependencies();
        addIllegalDependencies(illegalDependencies, union, others);
        if (!illegalDependencies.isEmpty()) {
            throw new AssertionError(violationsRenderer.render(illegalDependencies));
        }
        return this;
    }

    /**
     * Alternative for {@link #usesNot(Iterable)}.
     *
     * @param others the slices to check dependencies for
     * @return this instance (fluent API)
     * @see #usesNot(Iterable)
     */
    public SliceAssert usesNot(Slice... others) {
        return usesNot(Arrays.asList(others));
    }

    /**
     * Alias for {@link #usesNot(Iterable)}
     *
     * @param others the slices to check dependencies for
     * @return this instance (fluent API)
     */
    public SliceAssert doesNotUse(Iterable others) {
        return usesNot(others);
    }

    /**
     * Alias for {@link #usesNot(Slice...)}
     *
     * @param others the slices to check dependencies for
     * @return this instance (fluent API)
     */
    public SliceAssert doesNotUse(Slice... others) {
        return usesNot(others);
    }

    /**
     * Plural alias for {@link #usesNot(Iterable)}
     *
     * @param others the slices to check dependencies for
     * @return this instance (fluent API)
     */
    public SliceAssert doNotUse(Iterable others) {
        return usesNot(others);
    }

    /**
     * Plural alias for {@link #usesNot(Slice...)}
     *
     * @param others the slices to check dependencies for
     * @return this instance (fluent API)
     */
    public SliceAssert doNotUse(Slice... others) {
        return usesNot(others);
    }

    private void addIllegalDependencies(IllegalDependencies illegalDependencies, Slice slice, Iterable illegals) {
        for (Clazz clazz : slice.getClazzes()) {
            for (Clazz dependency : clazz.getDependencies().getClazzes()) {
                if (containsAny(illegals, dependency)) {
                    illegalDependencies.add(clazz, dependency);
                }
            }
        }
    }

    private boolean containsAny(Iterable slices, Clazz clazz) {
        for (Slice slice : slices) {
            if (slice.contains(clazz)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Assert there are no cyclic dependencies.
     *
     * @return this instance (fluent API)
     */
    public SliceAssert isCycleFree() {
        Map dependencies = mapDependencies();
        Dag dag = new Dag();
        for (Slice n : slices) {
            for (Slice m : slices) {
                if (n != m && n.uses(m)) {
                    dag.addEdge(n, m);
                }
            }
        }
        if (!dag.isCycleFree()) {
            String cycle = renderCycle(dag);
            throw new AssertionError(cycle);
        }
        return this;
    }

    /**
     * Plural alias for {@link #isCycleFree()}.
     *
     * @return this instance (fluent API)
     */
    public SliceAssert areCycleFree() {
        return isCycleFree();
    }

    /**
     * Assert there are no backward references and each slice uses only its direct successor.
     *
     * @return this instance (fluent API)
     */
    public SliceAssert isLayeredStrict() {
        IllegalDependencies illegalDependencies = new IllegalDependencies();
        List list = asList();
        if (list.size() < 2) {
            return this;
        }

        for (int i = list.size() - 1; i >= 0; i--) {
            // disallow backward dependencies
            if (i > 0) {
                addIllegalDependencies(illegalDependencies, list.get(i), list.subList(0, i));
            }
            // disallow forward dependencies skipping one layer
            if (i + 2 < list.size()) {
                addIllegalDependencies(illegalDependencies, list.get(i), list.subList(i + 2, list.size()));
            }
        }

        if (!illegalDependencies.isEmpty()) {
            throw new AssertionError(violationsRenderer.render(illegalDependencies));
        }
        return this;
    }

    /**
     * Plural alias for {@link #isLayeredStrict()}.
     *
     * @return this instance (fluent API)
     */
    public SliceAssert areLayeredStrict() {
        return isLayeredStrict();
    }

    /**
     * Assert there are no backward references.
     *
     * @return this instance (fluent API)
     */
    public SliceAssert isLayeredRelaxed() {
        IllegalDependencies illegalDependencies = new IllegalDependencies();
        List list = asList();
        if (list.size() < 2) {
            return this;
        }

        for (int i = list.size() - 1; i > 0; i--) {
            // disallow backward dependencies
            addIllegalDependencies(illegalDependencies, list.get(i), list.subList(0, i));
        }

        if (!illegalDependencies.isEmpty()) {
            throw new AssertionError(violationsRenderer.render(illegalDependencies));
        }
        return this;
    }

    /**
     * Plural alias for {@link #isLayeredRelaxed()} ()}.
     *
     * @return this instance (fluent API)
     */
    public SliceAssert areLayeredRelaxed() {
        return isLayeredRelaxed();
    }

    private List asList() {
        List list = new ArrayList();
        for (Slice slice : slices) {
            list.add(slice);
        }
        return list;
    }

    private Map mapDependencies() {
        Map dependencies = new HashMap();
        for (Slice slice : slices) {
            dependencies.put(slice, slice.getDependencies());
        }
        return dependencies;
    }

    private String renderCycle(Dag dag) {
        return cycleRenderer.renderCycle(dag);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy