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

Extend syntax to query different kinds of classes #245

Merged
merged 8 commits into from
Jan 10, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import static com.tngtech.archunit.base.DescribedPredicate.anyElementThat;
import static com.tngtech.archunit.base.DescribedPredicate.doNot;
import static com.tngtech.archunit.base.DescribedPredicate.not;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.ANONYMOUS_CLASSES;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.assignableTo;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.equivalentTo;
import static com.tngtech.archunit.core.domain.JavaClass.Predicates.resideInAPackage;
Expand Down Expand Up @@ -188,15 +189,6 @@ private boolean enumMethod(JavaMethod methodToCheck, String name, Class<?>... pa
};
}

private static DescribedPredicate<JavaClass> anonymousClass() {
return new DescribedPredicate<JavaClass>("anonymous class") {
@Override
public boolean apply(JavaClass input) {
return input.isAnonymous();
}
};
}

private static DescribedPredicate<JavaMember> declaredInClassIn(String packageIdentifier) {
return declaredIn(resideInAPackage(packageIdentifier).as("class in '%s'", packageIdentifier));
}
Expand Down Expand Up @@ -286,7 +278,7 @@ private boolean isPublicAPISuperMethod(JavaMethod candidate, JavaMethod methodTo
private static DescribedPredicate<JavaMember> relevantArchUnitMembers() {
return not(inheritedFromObjectOrEnum())
.and(not(declaredIn(assignableTo(Annotation.class))))
.and(not(declaredIn(anonymousClass())))
.and(not(declaredIn(ANONYMOUS_CLASSES)))
.and(not(declaredIn(internal())))
.and(not(declaredInClassIn(THIRDPARTY_PACKAGE_IDENTIFIER)))
.as("relevant members");
Expand Down
284 changes: 279 additions & 5 deletions archunit/src/main/java/com/tngtech/archunit/core/domain/JavaClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ public class JavaClass implements HasName.AndFullName, HasAnnotations<JavaClass>
private JavaPackage javaPackage;
private final boolean isInterface;
private final boolean isEnum;
private final boolean isAnonymousClass;
private final boolean isMemberClass;
private final Set<JavaModifier> modifiers;
private final Supplier<Class<?>> reflectSupplier;
private Set<JavaField> fields = emptySet();
Expand Down Expand Up @@ -102,6 +104,8 @@ public Set<JavaMember> get() {
javaType = checkNotNull(builder.getJavaType());
isInterface = builder.isInterface();
isEnum = builder.isEnum();
isAnonymousClass = builder.isAnonymousClass();
isMemberClass = builder.isMemberClass();
modifiers = checkNotNull(builder.getModifiers());
reflectSupplier = Suppliers.memoize(new ReflectClassSupplier());
sourceCodeLocation = SourceCodeLocation.of(this);
Expand Down Expand Up @@ -237,23 +241,241 @@ Optional<JavaClass> tryGetComponentType() {
}

/**
* @return Returns true if this class is declared within another class.
* Returns false for top-level classes.
* A <b>top level class</b> is a class that is not a nested class, i.e. not declared within the body
* of another class.<br><br>
*
* Example:
* <pre><code>
* public class TopLevel {
* class NestedNonStatic {}
* static class NestedStatic {}
*
* void method() {
* class NestedLocal {}
*
* new NestedAnonymous() {}
* }
* }
* </code></pre>
* Of all these class declarations only {@code TopLevel} is a top level class, since all
* other classes are declared within the body of {@code TopLevel} and are thereby nested classes.
* <br><br>
* Compare e.g. <a href="https://docs.oracle.com/javase/specs/jls/se13/html/jls-8.html">
* Java Language Specification</a>
*
* @see #isNestedClass()
* @see #isMemberClass()
* @see #isInnerClass()
* @see #isLocalClass()
* @see #isAnonymousClass()
* @return {@code true} if this class is a top level class, i.e. not nested inside of
* any other class, {@code false} otherwise
*/
@PublicAPI(usage = ACCESS)
public boolean isTopLevelClass() {
return !isNestedClass();
}

/**
* A <b>nested class</b> is any class whose declaration occurs
* within the body of another class or interface.<br><br>
*
* Example:
* <pre><code>
* public class TopLevel {
* class NestedNonStatic {}
* static class NestedStatic {}
*
* void method() {
* class NestedLocal {}
*
* new NestedAnonymous() {}
* }
* }
* </code></pre>
* All classes {@code NestedNonStatic}, {@code NestedStatic}, {@code NestedLocal} and the class
* the compiler creates for the anonymous class derived from {@code "new NestedAnonymous() {}"}
* (which will have some generated name like {@code TopLevel$1})
* are considered nested classes. {@code TopLevel} on the other side is no nested class.
* <br><br>
* Compare e.g. <a href="https://docs.oracle.com/javase/specs/jls/se13/html/jls-8.html">
* Java Language Specification</a>
*
* @see #isTopLevelClass()
* @see #isMemberClass()
* @see #isInnerClass()
* @see #isLocalClass()
* @see #isAnonymousClass()
* @return {@code true} if this class is nested, i.e. declared within another class,
* {@code false} otherwise (i.e. for top-level classes)
*/
@PublicAPI(usage = ACCESS)
public boolean isNestedClass() {
return enclosingClass.isPresent();
}

/**
* @return Returns true if this class is a non-static nested class.
* Returns false otherwise.
* A <b>member class</b> is a class whose declaration is <u>directly</u> enclosed
* in the body of another class or interface declaration.<br><br>
*
* Example:
* <pre><code>
* public class TopLevel {
* class MemberClassNonStatic {}
* static class MemberClassStatic {}
*
* void method() {
* class NoMemberLocal {}
*
* new NoMemberAnonymous() {}
* }
* }
* </code></pre>
* Both {@code MemberClassNonStatic} and {@code MemberClassStatic} are member classes,
* since they are directly declared within the body of {@code TopLevel}.
* On the other hand {@code NoMemberLocal} and the class
* the compiler creates for the anonymous class derived from {@code "new NoMemberAnonymous() {}"}
* (which will have some generated name like {@code TopLevel$1}), as well as {@code TopLevel}
* itself, are not considered member classes.
* <br><br>
* Compare e.g. <a href="https://docs.oracle.com/javase/specs/jls/se13/html/jls-8.html">
* Java Language Specification</a>
*
* @see #isTopLevelClass()
* @see #isNestedClass()
* @see #isInnerClass()
* @see #isLocalClass()
* @see #isAnonymousClass()
* @return {@code true} if this class is a member class, i.e. directly declared within
* the body of another class, {@code false} otherwise
*/
@PublicAPI(usage = ACCESS)
public boolean isMemberClass() {
return isNestedClass() && isMemberClass;
}

/**
* An <b>inner class</b> is a nested class that is not explicitly or implicitly declared static.<br><br>
*
* Example:
* <pre><code>
* public class TopLevel {
* class InnerMemberClass {}
* static class NoInnerClassSinceDeclaredStatic {}
* interface NoInnerClassSinceInterface {}
*
* void method() {
* class InnerLocalClass {}
*
* new InnerAnonymousClass() {}
* }
* }
* </code></pre>
* The classes {@code InnerMemberClass}, {@code InnerLocalClass} and the class
* the compiler creates for the anonymous class derived from {@code "new InnerAnonymousClass() {}"}
* (which will have some generated name like {@code TopLevel$1})
* are inner classes since they are nested but not static.
* On the other hand {@code NoInnerClassSinceDeclaredStatic}, {@code NoInnerClassSinceInterface} and {@code TopLevel}
* are no inner classes, because the former two explicitly or implicitly have the
* {@code static} modifier while the latter one is a top level class.
* <br><br>
* Compare e.g. <a href="https://docs.oracle.com/javase/specs/jls/se13/html/jls-8.html">
* Java Language Specification</a>
*
* @see #isTopLevelClass()
* @see #isNestedClass()
* @see #isMemberClass()
* @see #isLocalClass()
* @see #isAnonymousClass()
* @return {@code true} if this class is an inner class (i.e. nested but non-static)
* {@code false} otherwise
*/
@PublicAPI(usage = ACCESS)
public boolean isInnerClass() {
return isNestedClass() && !getModifiers().contains(JavaModifier.STATIC);
}

/**
* A <b>local class</b> is a nested class that is not a member of any class and that has a name.<br><br>
*
* Example:
* <pre><code>
* public class TopLevel {
* class InnerClass {}
* static class NestedStaticClass {}
*
* void method() {
* class LocalClass {}
*
* new AnonymousClass() {}
* }
* }
* </code></pre>
* Only The class {@code LocalClass} is a local class, since it is a nested class that is not a member
* class, but it has the name "LocalClass".<br>
* All the other classes {@code TopLevel}, {@code InnerClass}, {@code NestedStaticClass} and
* the class the compiler creates for the anonymous class derived from {@code "new AnonymousClass() {}"}
* (which will have some generated name like {@code TopLevel$1}) are considered non-local, since they
* either are top level, directly declared within the body of {@code TopLevel} or are anonymous
* and thus have no name.
* <br><br>
* Compare e.g. <a href="https://docs.oracle.com/javase/specs/jls/se13/html/jls-8.html">
* Java Language Specification</a>
*
* @see #isTopLevelClass()
* @see #isNestedClass()
* @see #isMemberClass()
* @see #isInnerClass()
* @see #isAnonymousClass()
* @return {@code true} if this class is local class,
* {@code false} otherwise
*/
@PublicAPI(usage = ACCESS)
public boolean isLocalClass() {
return isNestedClass() && !isMemberClass() && !getSimpleName().isEmpty();
}

/**
* An <b>anonymous class</b> is an inner class that is automatically derived from a class
* creation expression with a declared class body, e.g. {@code new Example(){ <some-body> }}.<br>
* The compiler will automatically create a class backing this instance, typically with an
* autogenerated name like {@code SomeClass$1}, where {@code SomeClass} is the class holding
* the class creation expression.<br><br>
*
* Example:
* <pre><code>
* public class TopLevel {
* class InnerClass {}
* static class NestedStaticClass {}
*
* void method() {
* class LocalClass {}
*
* new AnonymousClass() {}
* }
* }
* </code></pre>
* Only the class the compiler creates for the anonymous class derived from {@code "new AnonymousClass() {}"}
* (which will have some generated name like {@code TopLevel$1}) is considered an anonymous class.<br>
* All the other classes {@code TopLevel}, {@code InnerClass}, {@code NestedStaticClass} and
* {@code LocalClass} are considered non-anonymous.
* <br><br>
* Compare e.g. <a href="https://docs.oracle.com/javase/specs/jls/se13/html/jls-8.html">
* Java Language Specification</a>
*
* @see #isTopLevelClass()
* @see #isNestedClass()
* @see #isMemberClass()
* @see #isInnerClass()
* @see #isLocalClass()
* @return {@code true} if this class is an anonymous class,
* {@code false} otherwise
*/
@PublicAPI(usage = ACCESS)
public boolean isAnonymousClass() {
return isAnonymousClass;
}

@Override
@PublicAPI(usage = ACCESS)
public Set<JavaModifier> getModifiers() {
Expand Down Expand Up @@ -945,9 +1167,13 @@ public static List<String> namesOf(List<Class<?>> paramTypes) {
return result;
}

/**
* @deprecated use {@link #isAnonymousClass()} instead
*/
@Deprecated
@PublicAPI(usage = ACCESS)
public boolean isAnonymous() {
return getSimpleName().isEmpty();
return isAnonymousClass();
}

public static final class Functions {
Expand Down Expand Up @@ -1113,6 +1339,54 @@ public boolean apply(JavaClass input) {
}
};

@PublicAPI(usage = ACCESS)
public static final DescribedPredicate<JavaClass> TOP_LEVEL_CLASSES = new DescribedPredicate<JavaClass>("top level classes") {
@Override
public boolean apply(JavaClass input) {
return input.isTopLevelClass();
}
};

@PublicAPI(usage = ACCESS)
public static final DescribedPredicate<JavaClass> NESTED_CLASSES = new DescribedPredicate<JavaClass>("nested classes") {
@Override
public boolean apply(JavaClass input) {
return input.isNestedClass();
}
};

@PublicAPI(usage = ACCESS)
public static final DescribedPredicate<JavaClass> MEMBER_CLASSES = new DescribedPredicate<JavaClass>("member classes") {
@Override
public boolean apply(JavaClass input) {
return input.isMemberClass();
}
};

@PublicAPI(usage = ACCESS)
public static final DescribedPredicate<JavaClass> INNER_CLASSES = new DescribedPredicate<JavaClass>("inner classes") {
@Override
public boolean apply(JavaClass input) {
return input.isInnerClass();
}
};

@PublicAPI(usage = ACCESS)
public static final DescribedPredicate<JavaClass> LOCAL_CLASSES = new DescribedPredicate<JavaClass>("local classes") {
@Override
public boolean apply(JavaClass input) {
return input.isLocalClass();
}
};

@PublicAPI(usage = ACCESS)
public static final DescribedPredicate<JavaClass> ANONYMOUS_CLASSES = new DescribedPredicate<JavaClass>("anonymous classes") {
@Override
public boolean apply(JavaClass input) {
return input.isAnonymousClass();
}
};

@PublicAPI(usage = ACCESS)
public static DescribedPredicate<JavaClass> type(final Class<?> type) {
return equalTo(type.getName()).<JavaClass>onResultOf(GET_NAME).as("type " + type.getName());
Expand Down
Loading