Skip to content

Commit

Permalink
Merge pull request #156 from TNG/extend_slice_creation
Browse files Browse the repository at this point in the history
Extend slice creation
  • Loading branch information
codecholeric authored Mar 10, 2019
2 parents ee43a5e + c5d089d commit 749e806
Show file tree
Hide file tree
Showing 13 changed files with 568 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.junit.ArchUnitRunner;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.library.dependencies.SliceAssignment;
import com.tngtech.archunit.library.dependencies.SliceIdentifier;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;

Expand Down Expand Up @@ -54,4 +56,30 @@ public class CyclicDependencyRulesTest {
.should().beFreeOfCycles()
.ignoreDependency(SliceOneCallingConstructorInSliceTwoAndMethodInSliceThree.class, ClassCallingConstructorInSliceFive.class)
.ignoreDependency(resideInAPackage("..slice4.."), DescribedPredicate.<JavaClass>alwaysTrue());

@ArchTest
public static final ArchRule no_cycles_in_freely_customized_slices =
slices().assignedFrom(inComplexSliceOneOrTwo())
.namingSlices("$1[$2]")
.should().beFreeOfCycles();

private static SliceAssignment inComplexSliceOneOrTwo() {
return new SliceAssignment() {
@Override
public String getDescription() {
return "complex slice one or two";
}

@Override
public SliceIdentifier getIdentifierOf(JavaClass javaClass) {
if (javaClass.getPackageName().contains("complexcycles.slice1")) {
return SliceIdentifier.of("Complex-Cycle", "One");
}
if (javaClass.getPackageName().contains("complexcycles.slice2")) {
return SliceIdentifier.of("Complex-Cycle", "Two");
}
return SliceIdentifier.ignore();
}
};
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.tngtech.archunit.exampletest.junit5;

import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.example.cycle.complexcycles.slice1.SliceOneCallingConstructorInSliceTwoAndMethodInSliceThree;
import com.tngtech.archunit.example.cycle.complexcycles.slice3.ClassCallingConstructorInSliceFive;
import com.tngtech.archunit.junit.AnalyzeClasses;
import com.tngtech.archunit.junit.ArchTag;
import com.tngtech.archunit.junit.ArchTest;
import com.tngtech.archunit.lang.ArchRule;
import com.tngtech.archunit.library.dependencies.SliceAssignment;
import com.tngtech.archunit.library.dependencies.SliceIdentifier;

import static com.tngtech.archunit.base.DescribedPredicate.alwaysTrue;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage;
Expand Down Expand Up @@ -50,4 +53,30 @@ public class CyclicDependencyRulesTest {
.should().beFreeOfCycles()
.ignoreDependency(SliceOneCallingConstructorInSliceTwoAndMethodInSliceThree.class, ClassCallingConstructorInSliceFive.class)
.ignoreDependency(resideInAPackage("..slice4.."), alwaysTrue());

@ArchTest
static final ArchRule no_cycles_in_freely_customized_slices =
slices().assignedFrom(inComplexSliceOneOrTwo())
.namingSlices("$1[$2]")
.should().beFreeOfCycles();

private static SliceAssignment inComplexSliceOneOrTwo() {
return new SliceAssignment() {
@Override
public String getDescription() {
return "complex slice one or two";
}

@Override
public SliceIdentifier getIdentifierOf(JavaClass javaClass) {
if (javaClass.getPackageName().contains("complexcycles.slice1")) {
return SliceIdentifier.of("Complex-Cycle", "One");
}
if (javaClass.getPackageName().contains("complexcycles.slice2")) {
return SliceIdentifier.of("Complex-Cycle", "Two");
}
return SliceIdentifier.ignore();
}
};
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.tngtech.archunit.exampletest;

import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.archunit.example.cycle.complexcycles.slice1.SliceOneCallingConstructorInSliceTwoAndMethodInSliceThree;
import com.tngtech.archunit.example.cycle.complexcycles.slice3.ClassCallingConstructorInSliceFive;
import com.tngtech.archunit.library.dependencies.SliceAssignment;
import com.tngtech.archunit.library.dependencies.SliceIdentifier;
import org.junit.Test;
import org.junit.experimental.categories.Category;

Expand Down Expand Up @@ -74,4 +77,32 @@ public void no_cycles_in_complex_scenario_with_custom_ignore() {
.ignoreDependency(resideInAPackage("..slice4.."), alwaysTrue())
.check(classes);
}

@Test
public void no_cycles_in_freely_customized_slices() {
slices().assignedFrom(inComplexSliceOneOrTwo())
.namingSlices("$1[$2]")
.should().beFreeOfCycles()
.check(classes);
}

private SliceAssignment inComplexSliceOneOrTwo() {
return new SliceAssignment() {
@Override
public String getDescription() {
return "complex slice one or two";
}

@Override
public SliceIdentifier getIdentifierOf(JavaClass javaClass) {
if (javaClass.getPackageName().contains("complexcycles.slice1")) {
return SliceIdentifier.of("Complex-Cycle", "One");
}
if (javaClass.getPackageName().contains("complexcycles.slice2")) {
return SliceIdentifier.of("Complex-Cycle", "Two");
}
return SliceIdentifier.ignore();
}
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -443,12 +443,19 @@ Stream<DynamicTest> CyclicDependencyRulesTest() {
.by(cycleFromComplexSlice1To2())
.by(cycleFromComplexSlice1To2To3To5())

.ofRule("slices assigned from complex slice one or two should be free of cycles")
.by(cycleFromComplexSlice1To2("Complex-Cycle[One]", "Complex-Cycle[Two]"))

.toDynamicTests();
}

private static CyclicErrorMatcher cycleFromComplexSlice1To2() {
return cycleFromComplexSlice1To2("slice1 of complexcycles", "slice2 of complexcycles");
}

private static CyclicErrorMatcher cycleFromComplexSlice1To2(String sliceOneDescription, String sliceTwoDescription) {
return cycle()
.from("slice1 of complexcycles")
.from(sliceOneDescription)
.by(callFromMethod(ClassOfMinimalCycleCallingSliceTwo.class, "callSliceTwo")
.toMethod(ClassOfMinimalCycleCallingSliceOne.class, "callSliceOne")
.inLine(9))
Expand All @@ -457,7 +464,7 @@ private static CyclicErrorMatcher cycleFromComplexSlice1To2() {
.by(callFromMethod(SliceOneCallingConstructorInSliceTwoAndMethodInSliceThree.class, "callSliceTwo")
.toConstructor(InstantiatedClassInSliceTwo.class)
.inLine(10))
.from("slice2 of complexcycles")
.from(sliceTwoDescription)
.by(inheritanceFrom(SliceTwoInheritingFromSliceOne.class)
.extending(SliceOneCallingConstructorInSliceTwoAndMethodInSliceThree.class))
.by(field(ClassOfMinimalCycleCallingSliceOne.class, "classInSliceOne")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,19 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static com.tngtech.archunit.PublicAPI.Usage.ACCESS;

/**
* A collection of {@link JavaClass JavaClasses} modelling some domain aspect of a code basis. This is conceptually
* a cut through a code base according to business logic. Take for example
* <pre><code>
* com.mycompany.myapp.order
* com.mycompany.myapp.customer
* com.mycompany.myapp.user
* com.mycompany.myapp.authorization
* </code></pre>
* The top level packages under 'myapp' could be considered slices according to different domain aspects.<br>
* Thus there could be a slice 'Order' housing all the classes from the {@code order} package, a slice 'Customer'
* housing all the classes from the {@code customer} package and so on.
*/
public final class Slice extends ForwardingSet<JavaClass> implements HasDescription, CanOverrideDescription<Slice> {
private final List<String> matchingGroups;
private Description description;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright 2019 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.library.dependencies;

import com.tngtech.archunit.PublicAPI;
import com.tngtech.archunit.base.HasDescription;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;

import static com.tngtech.archunit.PublicAPI.Usage.INHERITANCE;

/**
* A mapping {@link JavaClass} -&gt; {@link SliceIdentifier} which defines how to partition
* a set of {@link JavaClasses} into {@link Slices}. All classes that are mapped to the same
* {@link SliceIdentifier} will belong to the same {@link Slice}.<br>
* A {@link SliceAssignment} must provide a description to be used for the rule text of a rule
* formed via {@link SlicesRuleDefinition}.
*/
@PublicAPI(usage = INHERITANCE)
public interface SliceAssignment extends HasDescription {
SliceIdentifier getIdentifierOf(JavaClass javaClass);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright 2019 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.library.dependencies;

import java.util.Collections;
import java.util.List;
import java.util.Objects;

import com.google.common.collect.ImmutableList;
import com.tngtech.archunit.PublicAPI;
import com.tngtech.archunit.core.domain.JavaClass;
import com.tngtech.archunit.core.domain.JavaClasses;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.tngtech.archunit.PublicAPI.Usage.ACCESS;

/**
* An unique identifier of a {@link Slice}. All {@link JavaClasses} that are assigned to the same
* {@link SliceIdentifier} are considered to belong to the same {@link Slice}.<br>
* A {@link SliceIdentifier} consists of textual parts. Two {@link SliceIdentifier} are considered to
* be equal if and only if their parts are equal. The parts can also be referred to from
* {@link Slices#namingSlices(String)} via '{@code $x}' where '{@code x}' is the number of the part.
*/
public final class SliceIdentifier {
private final List<String> parts;

private SliceIdentifier(List<String> parts) {
this.parts = ImmutableList.copyOf(parts);
}

List<String> getParts() {
return parts;
}

@Override
public int hashCode() {
return Objects.hash(parts);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
final SliceIdentifier other = (SliceIdentifier) obj;
return Objects.equals(this.parts, other.parts);
}

@Override
public String toString() {
return getClass().getSimpleName() + parts;
}

@PublicAPI(usage = ACCESS)
public static SliceIdentifier of(String... parts) {
return of(ImmutableList.copyOf(parts));
}

@PublicAPI(usage = ACCESS)
public static SliceIdentifier of(List<String> parts) {
checkNotNull(parts, "Supplied parts may not be null");
checkArgument(!parts.isEmpty(),
"Parts of a %s must not be empty. Use %s.ignore() to ignore a %s",
SliceIdentifier.class.getSimpleName(), SliceIdentifier.class.getSimpleName(), JavaClass.class.getSimpleName());

return new SliceIdentifier(parts);
}

@PublicAPI(usage = ACCESS)
public static SliceIdentifier ignore() {
return new SliceIdentifier(Collections.<String>emptyList());
}
}
Loading

0 comments on commit 749e806

Please sign in to comment.