Skip to content

Commit

Permalink
Review: I think it is a more consistent API if we just treat all inne…
Browse files Browse the repository at this point in the history
…r classes the same, be they anonymous or named. Thus call the method "belongTo" and define "belong" as being the class or any nested inner class. We should extend that to nested levels as well, since using an anonymous class within an anonymous class should not be counted any different IMHO.

Also improved tests a little to actually test the nested case which is the more interesting one.

Signed-off-by: Peter Gafert <peter.gafert@tngtech.com>
  • Loading branch information
codecholeric committed Jun 17, 2019
1 parent 964eea5 commit 5cc80f8
Show file tree
Hide file tree
Showing 10 changed files with 228 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
Expand Down Expand Up @@ -727,16 +726,6 @@ public boolean isAssignableTo(DescribedPredicate<? super JavaClass> predicate) {
return anyMatches(possibleTargets, predicate);
}

/**
*
* @param clazz a class
* @return true if this {@link JavaClass} represents an anonymous inner class of the supplied {@link Class},
* otherwise false
*/
private boolean isAnonymousInnerClassOf(Class<?> clazz) {
return isInnerClass() && enclosingClass.get().isEquivalentTo(clazz) && isAnonymous();
}

private boolean anyMatches(List<JavaClass> possibleTargets, DescribedPredicate<? super JavaClass> predicate) {
for (JavaClass javaClass : possibleTargets) {
if (predicate.apply(javaClass)) {
Expand Down Expand Up @@ -1294,35 +1283,43 @@ 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> areAnyClass(Class... classes) {
return new AnyClassPredicate(classes);
public static DescribedPredicate<JavaClass> belongToAnyOf(Class... classes) {
return new BelongToAnyOfPredicate(classes);
}

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

AnyClassPredicate(Class... classes) {
super(String.format("any class %s", getClassNames(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 (input.isEquivalentTo(clazz) || input.isAnonymousInnerClassOf(clazz)) {
if (belongsTo(input, clazz)) {
return true;
}
}
return false;
}

private static List<String> getClassNames (Class[] classes) {
List<String> names = new ArrayList<>();
for (int i = 0; i < classes.length; i++) {
names.add(classes[i].getName());
private boolean belongsTo(JavaClass input, Class clazz) {
JavaClass toTest = input;
while (!toTest.isEquivalentTo(clazz) && toTest.getEnclosingClass().isPresent()) {
toTest = toTest.getEnclosingClass().get();
}
return names;
return toTest.isEquivalentTo(clazz);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,8 @@ public CONJUNCTION areNotInterfaces() {
}

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

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,8 +370,8 @@ public CONJUNCTION areNotInterfaces() {
}

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

private CONJUNCTION givenWith(DescribedPredicate<? super JavaClass> predicate) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -627,12 +627,26 @@ public interface ClassesThat<CONJUNCTION> {
CONJUNCTION areNotInterfaces();

/**
* Matches every class in the supplied list and their anonymous inner classes.
* Matches every class in the supplied list and any of their named/anonymous inner classes,
* no matter how deeply nested. E.g. consider
*
* @param classes list of {@link Class} objects.
* <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 areAnyClass(Class... classes);
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 @@ -46,13 +46,6 @@ public class GivenClassesThatTest {
@Rule
public final ExpectedException thrown = ExpectedException.none();

@Test
public void areAnyClass() {
List<JavaClass> classes = filterResultOf(classes().that().areAnyClass(List.class, String.class))
.on(List.class, String.class, Iterable.class, StringBuilder.class);
assertThatClasses(classes).matchInAnyOrder(String.class, List.class);
}

@Test
public void haveFullyQualifiedName() {
List<JavaClass> classes = filterResultOf(classes().that().haveFullyQualifiedName(List.class.getName()))
Expand Down Expand Up @@ -642,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 @@ -750,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 @@ -43,14 +43,6 @@ public class GivenMembersDeclaredInClassesThatTest {
@Rule
public final ExpectedException thrown = ExpectedException.none();

@Test
public void areAnyClass() {
List<JavaMember> members = filterResultOf(members().that().areDeclaredInClassesThat().areAnyClass(String.class, List.class))
.on(List.class, String.class, Iterable.class);

assertThatMembers(members).matchInAnyOrderMembersOf(String.class, List.class);
}

@Test
public void haveFullyQualifiedName() {
List<JavaMember> members = filterResultOf(members().that().areDeclaredInClassesThat().haveFullyQualifiedName(List.class.getName()))
Expand Down Expand Up @@ -640,6 +632,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 @@ -726,4 +732,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;
}
}
}
}

0 comments on commit 5cc80f8

Please sign in to comment.