Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add classes().that().areAnyClass(...) to syntax #173

Merged
merged 4 commits into from
Jul 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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() {
}
}
}