diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAnnotation.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAnnotation.java index ecf9df196a..74997d2dc9 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAnnotation.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaAnnotation.java @@ -80,6 +80,14 @@ public final class JavaAnnotation implements HasTy private final String description; private final Map values; + private JavaAnnotation(JavaClass type, OWNER owner, CanBeAnnotated annotatedElement, String description, Map values) { + this.type = checkNotNull(type); + this.owner = checkNotNull(owner); + this.annotatedElement = checkNotNull(annotatedElement); + this.description = checkNotNull(description); + this.values = checkNotNull(values); + } + JavaAnnotation(OWNER owner, JavaAnnotationBuilder builder) { this.type = checkNotNull(builder.getType()); this.owner = checkNotNull(owner); @@ -88,7 +96,7 @@ public final class JavaAnnotation implements HasTy this.values = checkNotNull(builder.getValues(this)); } - private CanBeAnnotated getAnnotatedElement(Object owner) { + private static CanBeAnnotated getAnnotatedElement(Object owner) { Object candiate = owner; while (!(candiate instanceof JavaClass) && !(candiate instanceof JavaMember) && (candiate instanceof HasOwner)) { candiate = ((HasOwner) candiate).getOwner(); @@ -135,6 +143,10 @@ public OWNER getOwner() { return owner; } + JavaAnnotation withOwner(NEW_OWNER newOwner) { + return new JavaAnnotation<>(type, newOwner, annotatedElement, description, values); + } + /** * Returns either the element annotated with this {@link JavaAnnotation} (a class or member) * or in case this annotation is an annotation parameter, the element annotated with an diff --git a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaPackage.java b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaPackage.java index f4c8d83e97..d62a34e80d 100644 --- a/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaPackage.java +++ b/archunit/src/main/java/com/tngtech/archunit/core/domain/JavaPackage.java @@ -15,6 +15,7 @@ */ package com.tngtech.archunit.core.domain; +import java.lang.annotation.Annotation; import java.util.Collection; import java.util.Deque; import java.util.HashMap; @@ -24,6 +25,7 @@ import java.util.Set; import com.google.common.base.Splitter; +import com.google.common.collect.FluentIterable; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; @@ -31,6 +33,7 @@ import com.tngtech.archunit.PublicAPI; import com.tngtech.archunit.base.ChainableFunction; import com.tngtech.archunit.base.DescribedPredicate; +import com.tngtech.archunit.base.Function; import com.tngtech.archunit.base.Optional; import com.tngtech.archunit.base.Predicate; import com.tngtech.archunit.core.domain.properties.HasAnnotations; @@ -42,15 +45,17 @@ import static com.tngtech.archunit.PublicAPI.Usage.ACCESS; import static com.tngtech.archunit.PublicAPI.Usage.INHERITANCE; import static com.tngtech.archunit.base.DescribedPredicate.equalTo; +import static com.tngtech.archunit.base.Guava.toGuava; import static com.tngtech.archunit.core.domain.JavaClass.Functions.GET_SIMPLE_NAME; import static com.tngtech.archunit.core.domain.properties.HasName.Functions.GET_NAME; +import static java.util.Collections.emptySet; import static java.util.Collections.singleton; -public final class JavaPackage implements HasName { +public final class JavaPackage implements HasName, HasAnnotations { private final String name; private final String relativeName; private final Set classes; - private final Optional packageInfo; + private final Optional packageInfo; private final Map subPackages; private Optional parent = Optional.absent(); @@ -80,16 +85,110 @@ public String getRelativeName() { } @PublicAPI(usage = ACCESS) - public HasAnnotations getPackageInfo() { + public HasAnnotations getPackageInfo() { return tryGetPackageInfo().getOrThrow( new IllegalArgumentException(String.format("Package %s does not contain a package-info.java", getName()))); } @PublicAPI(usage = ACCESS) - public Optional tryGetPackageInfo() { + public Optional> tryGetPackageInfo() { return packageInfo; } + @Override + @PublicAPI(usage = ACCESS) + public Set> getAnnotations() { + if (packageInfo.isPresent()) { + return FluentIterable.from(packageInfo.get().getAnnotations()).transform(toGuava(withSelfAsOwner)).toSet(); + } + return emptySet(); + } + + @Override + @PublicAPI(usage = ACCESS) + public A getAnnotationOfType(Class type) { + return getAnnotationOfType(type.getName()).as(type); + } + + @Override + @PublicAPI(usage = ACCESS) + public JavaAnnotation getAnnotationOfType(String typeName) { + return tryGetAnnotationOfType(typeName).getOrThrow(new IllegalArgumentException( + String.format("Package %s is not annotated with @%s", getName(), typeName))); + } + + @Override + @PublicAPI(usage = ACCESS) + public Optional tryGetAnnotationOfType(Class type) { + if (packageInfo.isPresent()) { + return packageInfo.get().tryGetAnnotationOfType(type); + } + return Optional.absent(); + } + + @Override + @PublicAPI(usage = ACCESS) + public Optional> tryGetAnnotationOfType(String typeName) { + if (packageInfo.isPresent()) { + return packageInfo.get().tryGetAnnotationOfType(typeName).transform(withSelfAsOwner); + } + return Optional.absent(); + } + + @Override + @PublicAPI(usage = ACCESS) + public boolean isAnnotatedWith(Class annotationType) { + if (packageInfo.isPresent()) { + return packageInfo.get().isAnnotatedWith(annotationType); + } + return false; + } + + @Override + @PublicAPI(usage = ACCESS) + public boolean isAnnotatedWith(String annotationTypeName) { + if (packageInfo.isPresent()) { + return packageInfo.get().isAnnotatedWith(annotationTypeName); + } + return false; + } + + @Override + @PublicAPI(usage = ACCESS) + public boolean isAnnotatedWith(DescribedPredicate> predicate) { + if (packageInfo.isPresent()) { + return packageInfo.get().isAnnotatedWith(predicate); + } + return false; + } + + @Override + @PublicAPI(usage = ACCESS) + public boolean isMetaAnnotatedWith(Class annotationType) { + if (packageInfo.isPresent()) { + return packageInfo.get().isMetaAnnotatedWith(annotationType); + } + return false; + } + + @Override + @PublicAPI(usage = ACCESS) + public boolean isMetaAnnotatedWith(String annotationTypeName) { + if (packageInfo.isPresent()) { + return packageInfo.get().isMetaAnnotatedWith(annotationTypeName); + } + return false; + } + + @Override + @PublicAPI(usage = ACCESS) + public boolean isMetaAnnotatedWith(DescribedPredicate> predicate) { + if (packageInfo.isPresent()) { + return packageInfo.get().isMetaAnnotatedWith(predicate); + } + return false; + } + /** * @return the parent package, e.g. {@code java} for package {@code java.lang} */ @@ -387,11 +486,24 @@ public void accept(Predicate predicate, PackageVisitor visi } } + @Override + public String getDescription() { + return "Package <" + name + ">"; + } + @Override public String toString() { return getClass().getSimpleName() + "[" + getName() + "]"; } + private final Function, JavaAnnotation> withSelfAsOwner = + new Function, JavaAnnotation>() { + @Override + public JavaAnnotation apply(JavaAnnotation input) { + return input.withOwner(JavaPackage.this); + } + }; + static JavaPackage simple(JavaClass javaClass) { String packageName = javaClass.getPackageName(); JavaPackage defaultPackage = JavaPackage.from(singleton(javaClass)); diff --git a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaPackageTest.java b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaPackageTest.java index 813883348d..7b4c21845f 100644 --- a/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaPackageTest.java +++ b/archunit/src/test/java/com/tngtech/archunit/core/domain/JavaPackageTest.java @@ -14,6 +14,7 @@ import com.tngtech.archunit.base.Predicate; import com.tngtech.archunit.core.domain.JavaPackage.ClassVisitor; import com.tngtech.archunit.core.domain.JavaPackage.PackageVisitor; +import com.tngtech.archunit.core.domain.packageexamples.annotated.PackageLevelAnnotation; import com.tngtech.archunit.core.domain.packageexamples.first.First1; import com.tngtech.archunit.core.domain.packageexamples.first.First2; import com.tngtech.archunit.core.domain.packageexamples.second.ClassDependingOnOtherSecondClass; @@ -28,6 +29,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; +import static com.google.common.collect.Iterables.getOnlyElement; import static com.tngtech.archunit.core.domain.JavaClass.Functions.GET_SIMPLE_NAME; import static com.tngtech.archunit.core.domain.JavaPackage.Functions.GET_CLASSES; import static com.tngtech.archunit.core.domain.JavaPackage.Functions.GET_RELATIVE_NAME; @@ -101,6 +103,7 @@ public void creates_single_package() { JavaPackage javaPackage = defaultPackage.getPackage("java.lang"); assertThat(javaPackage.getName()).isEqualTo("java.lang"); + assertThat(javaPackage.getDescription()).isEqualTo("Package "); assertThat(javaPackage.getRelativeName()).isEqualTo("lang"); assertThatClasses(javaPackage.getClasses()).contain(Object.class, String.class); } @@ -346,6 +349,120 @@ public void test_tryGetPackageInfo() { assertThat(notAnnotatedPackage.tryGetPackageInfo()).isAbsent(); } + @Test + public void test_getAnnotations() { + JavaPackage annotatedPackage = importPackage("packageexamples.annotated"); + JavaPackage notAnnotatedPackage = importPackage("packageexamples"); + + JavaAnnotation annotation = getOnlyElement(annotatedPackage.getAnnotations()); + assertThat(annotation.getRawType()).matches(PackageLevelAnnotation.class); + assertThat(annotation.getOwner()).isEqualTo(annotatedPackage); + + assertThat(notAnnotatedPackage.getAnnotations()).isEmpty(); + } + + @Test + public void test_getAnnotationOfType_type() { + final JavaPackage annotatedPackage = importPackage("packageexamples.annotated"); + final JavaPackage notAnnotatedPackage = importPackage("packageexamples"); + + assertThat(annotatedPackage.getAnnotationOfType(PackageLevelAnnotation.class)).isInstanceOf(PackageLevelAnnotation.class); + + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + @Override + public void call() { + annotatedPackage.getAnnotationOfType(Deprecated.class); + } + }).isInstanceOf(IllegalArgumentException.class).hasMessageContaining(".packageexamples.annotated is not annotated with @"); + + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + @Override + public void call() { + notAnnotatedPackage.getAnnotationOfType(Deprecated.class); + } + }).isInstanceOf(IllegalArgumentException.class).hasMessageContaining(".packageexamples is not annotated with @"); + } + + @Test + public void test_getAnnotationOfType_typeName() { + final JavaPackage annotatedPackage = importPackage("packageexamples.annotated"); + final JavaPackage notAnnotatedPackage = importPackage("packageexamples"); + + assertThat(annotatedPackage.getAnnotationOfType("com.tngtech.archunit.core.domain.packageexamples.annotated.PackageLevelAnnotation") + .getRawType()).matches(PackageLevelAnnotation.class); + + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + @Override + public void call() { + annotatedPackage.getAnnotationOfType("java.lang.Deprecated"); + } + }).isInstanceOf(IllegalArgumentException.class).hasMessageContaining(".packageexamples.annotated is not annotated with @"); + + assertThatThrownBy(new ThrowableAssert.ThrowingCallable() { + @Override + public void call() { + notAnnotatedPackage.getAnnotationOfType("java.lang.Deprecated"); + } + }).isInstanceOf(IllegalArgumentException.class).hasMessageContaining(".packageexamples is not annotated with @"); + } + + @Test + public void test_tryGetAnnotationOfType_type() { + JavaPackage annotatedPackage = importPackage("packageexamples.annotated"); + JavaPackage notAnnotatedPackage = importPackage("packageexamples"); + + assertThat(annotatedPackage.tryGetAnnotationOfType(PackageLevelAnnotation.class)).isPresent(); + assertThat(annotatedPackage.tryGetAnnotationOfType(Deprecated.class)).isAbsent(); + + assertThat(notAnnotatedPackage.tryGetAnnotationOfType(Deprecated.class)).isAbsent(); + } + + @Test + public void test_tryGetAnnotationOfType_typeName() { + JavaPackage annotatedPackage = importPackage("packageexamples.annotated"); + JavaPackage notAnnotatedPackage = importPackage("packageexamples"); + + assertThat(annotatedPackage.tryGetAnnotationOfType( + "com.tngtech.archunit.core.domain.packageexamples.annotated.PackageLevelAnnotation")).isPresent(); + assertThat(annotatedPackage.tryGetAnnotationOfType("java.lang.Deprecated")).isAbsent(); + + assertThat(notAnnotatedPackage.tryGetAnnotationOfType("java.lang.Deprecated")).isAbsent(); + } + + @Test + public void test_isAnnotatedWith_type() { + JavaPackage annotatedPackage = importPackage("packageexamples.annotated"); + JavaPackage notAnnotatedPackage = importPackage("packageexamples"); + + assertThat(annotatedPackage.isAnnotatedWith(PackageLevelAnnotation.class)).isTrue(); + assertThat(annotatedPackage.isAnnotatedWith(Deprecated.class)).isFalse(); + + assertThat(notAnnotatedPackage.isAnnotatedWith(Deprecated.class)).isFalse(); + } + + @Test + public void test_isAnnotatedWith_typeName() { + JavaPackage annotatedPackage = importPackage("packageexamples.annotated"); + JavaPackage notAnnotatedPackage = importPackage("packageexamples"); + + assertThat(annotatedPackage.isAnnotatedWith("com.tngtech.archunit.core.domain.packageexamples.annotated.PackageLevelAnnotation")).isTrue(); + assertThat(annotatedPackage.isAnnotatedWith("java.lang.Deprecated")).isFalse(); + + assertThat(notAnnotatedPackage.isAnnotatedWith("java.lang.Deprecated")).isFalse(); + } + + @Test + public void test_isAnnotatedWith_predicate() { + JavaPackage annotatedPackage = importPackage("packageexamples.annotated"); + JavaPackage notAnnotatedPackage = importPackage("packageexamples"); + + assertThat(annotatedPackage.isAnnotatedWith(DescribedPredicate.>alwaysTrue())).isTrue(); + assertThat(annotatedPackage.isAnnotatedWith(DescribedPredicate.>alwaysFalse())).isFalse(); + + assertThat(notAnnotatedPackage.isAnnotatedWith(DescribedPredicate.>alwaysTrue())).isFalse(); + assertThat(notAnnotatedPackage.isAnnotatedWith(DescribedPredicate.>alwaysFalse())).isFalse(); + } + @Test public void function_GET_RELATIVE_NAME() { JavaPackage defaultPackage = importDefaultPackage(Object.class); @@ -406,4 +523,4 @@ private JavaPackage importPackage(String subPackageName) { JavaClasses classes = new ClassFileImporter().importPackages(packageName); return classes.getPackage(packageName); } -} \ No newline at end of file +}