Skip to content

Commit

Permalink
Merge pull request #173 from moboa/by-list-of-classes
Browse files Browse the repository at this point in the history
Add classes().that().areAnyClass(...) to syntax
  • Loading branch information
codecholeric committed Jul 29, 2019
2 parents 81c31f6 + c90991f commit da47f62
Show file tree
Hide file tree
Showing 10 changed files with 265 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1283,6 +1283,46 @@ public static DescribedPredicate<JavaClass> equivalentTo(final Class<?> clazz) {
return new EquivalentToPredicate(clazz);
}

/**
* A predicate to determine if a {@link JavaClass} "belongs" to one of the passed {@link Class classes},
* where we define "belong" as being equivalent to the class itself or any inner/anonymous class of this class.
*
* @param classes The {@link Class classes} to check the {@link JavaClass} against
* @return A {@link DescribedPredicate} returning true, if and only if the tested {@link JavaClass} is equivalent to
* one of the supplied {@link Class classes} or to one of its inner/anonymous classes.
*/
@PublicAPI(usage = ACCESS)
public static DescribedPredicate<JavaClass> belongToAnyOf(Class... classes) {
return new BelongToAnyOfPredicate(classes);
}

private static class BelongToAnyOfPredicate extends DescribedPredicate<JavaClass> {
private final Class[] classes;

BelongToAnyOfPredicate(Class... classes) {
super("belong to any of " + JavaClass.namesOf(classes));
this.classes = classes;
}

@Override
public boolean apply(JavaClass input) {
for (Class clazz : classes) {
if (belongsTo(input, clazz)) {
return true;
}
}
return false;
}

private boolean belongsTo(JavaClass input, Class clazz) {
JavaClass toTest = input;
while (!toTest.isEquivalentTo(clazz) && toTest.getEnclosingClass().isPresent()) {
toTest = toTest.getEnclosingClass().get();
}
return toTest.isEquivalentTo(clazz);
}
}

private static class SimpleNameStartingWithPredicate extends DescribedPredicate<JavaClass> {
private final String prefix;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,11 @@ public CONJUNCTION areNotInterfaces() {
return givenWith(are(not(JavaClass.Predicates.INTERFACES)));
}

@Override
public CONJUNCTION belongToAnyOf(Class... classes) {
return givenWith(JavaClass.Predicates.belongToAnyOf(classes));
}

@Override
public CONJUNCTION arePublic() {
return givenWith(SyntaxPredicates.arePublic());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,11 @@ public CONJUNCTION areNotInterfaces() {
return givenWith(are(not(INTERFACES)));
}

@Override
public CONJUNCTION belongToAnyOf(final Class... classes) {
return givenWith(JavaClass.Predicates.belongToAnyOf(classes));
}

private CONJUNCTION givenWith(DescribedPredicate<? super JavaClass> predicate) {
return predicateAggregator.apply(predicate);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -626,4 +626,27 @@ public interface ClassesThat<CONJUNCTION> {
@PublicAPI(usage = ACCESS)
CONJUNCTION areNotInterfaces();

/**
* Matches every class in the supplied list and any of their named/anonymous inner classes,
* no matter how deeply nested. E.g. consider
*
* <pre><code>
* class Outer {
* class Inner {
* class EvenMoreInner {
* }
* }
* }
* </code></pre>
*
* Then {@link #belongToAnyOf belongToAnyOf(Outer.class)} would match the {@link JavaClass}
* {@code Outer} but also {@code Inner} and {@code EvenMoreInner}.
* Likewise would hold for any anonymous inner classes.
*
* @param classes List of {@link Class} objects.
* @return A syntax conjunction element, which can be completed to form a full rule
*/
@PublicAPI(usage = ACCESS)
CONJUNCTION belongToAnyOf(Class... classes);

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.tngtech.archunit.core.domain.testobjects.InterfaceForA;
import com.tngtech.archunit.core.domain.testobjects.IsArrayTestClass;
import com.tngtech.archunit.core.domain.testobjects.SuperA;
import com.tngtech.archunit.core.importer.ClassFileImporter;
import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
Expand All @@ -34,9 +35,11 @@
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;

import static com.google.common.collect.Iterables.getOnlyElement;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.INTERFACES;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.assignableFrom;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.assignableTo;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.belongToAnyOf;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.equivalentTo;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.implement;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage;
Expand All @@ -47,6 +50,7 @@
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.simpleNameStartingWith;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.type;
import static com.tngtech.archunit.core.domain.JavaConstructor.CONSTRUCTOR_NAME;
import static com.tngtech.archunit.core.domain.JavaFieldAccess.AccessType.SET;
import static com.tngtech.archunit.core.domain.TestUtils.importClassWithContext;
import static com.tngtech.archunit.core.domain.TestUtils.importClasses;
import static com.tngtech.archunit.core.domain.TestUtils.importClassesWithContext;
Expand Down Expand Up @@ -705,6 +709,39 @@ public void predicate_equivalentTo() {
.hasDescription("equivalent to " + Parent.class.getName());
}

@Test
public void predicate_belong_to() {
JavaClasses classes = new ClassFileImporter().importPackagesOf(getClass());
JavaClass outerAnonymous =
getOnlyClassSettingField(classes, ClassWithNamedAndAnonymousInnerClasses.name_of_fieldIndicatingOuterAnonymousInnerClass);
JavaClass nestedAnonymous =
getOnlyClassSettingField(classes, ClassWithNamedAndAnonymousInnerClasses.name_of_fieldIndicatingNestedAnonymousInnerClass);

assertThat(belongToAnyOf(Object.class, ClassWithNamedAndAnonymousInnerClasses.class))
.hasDescription(String.format("belong to any of [%s, %s]",
Object.class.getName(), ClassWithNamedAndAnonymousInnerClasses.class.getName()))
.accepts(classes.get(ClassWithNamedAndAnonymousInnerClasses.class))
.accepts(classes.get(ClassWithNamedAndAnonymousInnerClasses.NamedInnerClass.class))
.accepts(classes.get(ClassWithNamedAndAnonymousInnerClasses.NamedInnerClass.NestedNamedInnerClass.class))
.accepts(outerAnonymous)
.accepts(nestedAnonymous)
.rejects(classes.get(getClass()));
}

private JavaClass getOnlyClassSettingField(JavaClasses classes, final String fieldName) {
return getOnlyElement(classes.that(new DescribedPredicate<JavaClass>("") {
@Override
public boolean apply(JavaClass input) {
for (JavaFieldAccess access : input.getFieldAccessesFromSelf()) {
if (access.getTarget().getName().equals(fieldName) && access.getAccessType() == SET) {
return true;
}
}
return false;
}
}));
}

private JavaClass classWithHierarchy(Class<?> clazz) {
Set<Class<?>> classesToImport = getHierarchy(clazz);
return importClasses(classesToImport.toArray(new Class[0])).get(clazz);
Expand Down Expand Up @@ -1037,4 +1074,31 @@ class Members {
void parentMethod();
}

private static class ClassWithNamedAndAnonymousInnerClasses {
static final String name_of_fieldIndicatingOuterAnonymousInnerClass = "fieldIndicatingOuterAnonymousInnerClass";
static final String name_of_fieldIndicatingNestedAnonymousInnerClass = "fieldIndicatingNestedAnonymousInnerClass";

private Object fieldIndicatingOuterAnonymousInnerClass;
private Object fieldIndicatingNestedAnonymousInnerClass;

void createAnonymousClasses() {
new Runnable() {
@Override
public void run() {
fieldIndicatingOuterAnonymousInnerClass = "set";
new Runnable() {
@Override
public void run() {
fieldIndicatingNestedAnonymousInnerClass = "set";
}
};
}
};
}

private class NamedInnerClass {
private class NestedNamedInnerClass {
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,10 @@ boolean matches(String line, JavaClass javaClass) {
private static class ClassOriginInLineMatcher extends ClassInReportLineMatcher {
@Override
boolean matches(String line, JavaClass javaClass) {
String originPattern = String.format(".*<%s.*(\\w\\.)+\\w.*in \\(.*\\.java.*\\)", quote(javaClass.getName()));
String optionalMemberName = "(\\.[^$]+)?";
String optionalMethodParameters = "(\\([^)]*\\))?";
String originPattern = String.format(".*<%s%s%s> .* <.*> in \\(.*\\.java.*\\)",
quote(javaClass.getName()), optionalMemberName, optionalMethodParameters);
return line.matches(originPattern);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -635,6 +635,17 @@ public void areNotInterfaces_predicate() {
assertThatClasses(classes).matchInAnyOrder(String.class, Integer.class);
}

@Test
public void belongToAnyOf() {
List<JavaClass> classes = filterResultOf(classes().that().belongToAnyOf(ClassWithInnerClasses.class, String.class))
.on(ClassWithInnerClasses.class, ClassWithInnerClasses.InnerClass.class, ClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class,
List.class, String.class, Iterable.class, StringBuilder.class);

assertThatClasses(classes).matchInAnyOrder(
ClassWithInnerClasses.class, ClassWithInnerClasses.InnerClass.class, ClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class,
String.class);
}

@Test
public void and_conjunction() {
List<JavaClass> classes = filterResultOf(
Expand Down Expand Up @@ -743,4 +754,11 @@ private static class AnnotatedClass {
@MetaAnnotatedAnnotation
private static class MetaAnnotatedClass {
}
}

private static class ClassWithInnerClasses {
private static class InnerClass {
private static class EvenMoreInnerClass {
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,20 @@ public void areNotInterfaces_predicate() {
assertThatMembers(members).matchInAnyOrderMembersOf(String.class, Integer.class);
}

@Test
public void belongToAnyOf() {
List<JavaMember> members =
filterResultOf(members().that().areDeclaredInClassesThat().belongToAnyOf(ClassWithInnerClasses.class, String.class))
.on(ClassWithInnerClasses.class, ClassWithInnerClasses.InnerClass.class,
ClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class,
List.class, String.class, Iterable.class, StringBuilder.class);

assertThatMembers(members).matchInAnyOrderMembersOf(
ClassWithInnerClasses.class, ClassWithInnerClasses.InnerClass.class, ClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class,
String.class
);
}

@Test
public void and_conjunction() {
List<JavaMember> members = filterResultOf(
Expand Down Expand Up @@ -719,4 +733,18 @@ private static class AnnotatedClass {
@MetaAnnotatedAnnotation
private static class MetaAnnotatedClass {
}
}

// the fields are important for the test to test anything relevant
@SuppressWarnings("unused")
private static class ClassWithInnerClasses {
String member;

private static class InnerClass {
String member;

private static class EvenMoreInnerClass {
String member;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,19 @@ public void areNotInterfaces_predicate(ClassesThat<ClassesShouldConjunction> noC
assertThatClasses(classes).matchInAnyOrder(ClassAccessingString.class, ClassAccessingSimpleClass.class);
}

@Test
@UseDataProvider("no_classes_should_that_rule_starts")
public void belongToAnyOf(ClassesThat<ClassesShouldConjunction> noClassesShouldThatRuleStart) {
Set<JavaClass> classes = filterClassesAppearingInFailureReport(
noClassesShouldThatRuleStart.belongToAnyOf(ClassWithInnerClasses.class, String.class))
.on(ClassAccessingNestedInnerClass.class, ClassWithInnerClasses.class, ClassWithInnerClasses.InnerClass.class,
ClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class, ClassAccessingString.class, ClassAccessingIterable.class);

assertThatClasses(classes).matchInAnyOrder(ClassAccessingNestedInnerClass.class,
ClassWithInnerClasses.class, ClassWithInnerClasses.InnerClass.class, ClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class,
ClassAccessingString.class);
}

@Test
@UseDataProvider("classes_should_only_that_rule_starts")
public void only_haveFullyQualifiedName(ClassesThat<ClassesShouldConjunction> classesShouldOnlyThatRuleStart) {
Expand Down Expand Up @@ -1566,4 +1579,22 @@ private static class AnnotatedClass {
@MetaAnnotatedAnnotation
private static class MetaAnnotatedClass {
}
}

@SuppressWarnings("unused")
private static class ClassAccessingNestedInnerClass {
ClassWithInnerClasses.InnerClass.EvenMoreInnerClass evenMoreInnerClass;

void access() {
evenMoreInnerClass.callMe();
}
}

private static class ClassWithInnerClasses {
private static class InnerClass {
private static class EvenMoreInnerClass {
void callMe() {
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -838,6 +838,20 @@ public void areNotInterfaces_predicate(ClassesThat<ClassesShouldConjunction> cla
assertThatClasses(classes).matchInAnyOrder(ClassBeingAccessedByInterface.class, InterfaceAccessingAClass.class);
}

@Test
@UseDataProvider("should_only_be_by_rule_starts")
public void belongToAnyOf(ClassesThat<ClassesShouldConjunction> classesShouldOnlyBeBy) {
Set<JavaClass> classes = filterViolationCausesInFailureReport(
classesShouldOnlyBeBy.belongToAnyOf(ClassWithInnerClasses.class))
.on(ClassWithInnerClasses.class, ClassWithInnerClasses.InnerClass.class,
ClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class,
AnotherClassWithInnerClasses.class, AnotherClassWithInnerClasses.InnerClass.class,
AnotherClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class,
ClassBeingAccessedByInnerClass.class);

assertThatClasses(classes).matchInAnyOrder(AnotherClassWithInnerClasses.InnerClass.EvenMoreInnerClass.class);
}

@DataProvider
public static Object[][] byClassesThat_predicate_rules() {
return testForEach(
Expand Down Expand Up @@ -1084,4 +1098,33 @@ private interface InterfaceBeingDependedOnByImplementing {

private static class ClassDependingViaImplementing implements InterfaceBeingDependedOnByImplementing {
}
}

private static class ClassWithInnerClasses {
private static class InnerClass {
private static class EvenMoreInnerClass {
ClassBeingAccessedByInnerClass classBeingAccessedByInnerClass;

void access() {
classBeingAccessedByInnerClass.callMe();
}
}
}
}

private static class AnotherClassWithInnerClasses {
private static class InnerClass {
private static class EvenMoreInnerClass {
ClassBeingAccessedByInnerClass classBeingAccessedByInnerClass;

void access() {
classBeingAccessedByInnerClass.callMe();
}
}
}
}

private static class ClassBeingAccessedByInnerClass {
void callMe() {
}
}
}

0 comments on commit da47f62

Please sign in to comment.