Skip to content

Commit e310a08

Browse files
authored
Merge pull request #57 from gdgib/G2-1779-InheritableMetadata
G2-1779 Inherit annotations as metadata from interfaces
2 parents 8f49b73 + 19c184e commit e310a08

File tree

5 files changed

+214
-16
lines changed

5 files changed

+214
-16
lines changed

ha-metadata/src/main/java/com/g2forge/habitat/metadata/access/annotation/AnnotationPredicate.java

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
package com.g2forge.habitat.metadata.access.annotation;
22

33
import java.lang.annotation.Annotation;
4+
import java.lang.annotation.Inherited;
5+
import java.util.ArrayList;
6+
import java.util.List;
7+
import java.util.Optional;
8+
import java.util.stream.Collectors;
49

10+
import com.g2forge.alexandria.java.core.helpers.HCollection;
11+
import com.g2forge.alexandria.java.core.helpers.HTree;
512
import com.g2forge.alexandria.java.reflect.annotations.IJavaAnnotations;
613
import com.g2forge.habitat.metadata.annotations.ContainerAnnotationReflection;
714
import com.g2forge.habitat.metadata.type.predicate.IAnnotationPredicateType;
@@ -30,25 +37,67 @@ public class AnnotationPredicate<T extends Annotation> implements IPredicate<T>
3037
@Override
3138
public T get0() {
3239
final Class<T> annotationType = getType().getAnnotationType();
33-
final IJavaAnnotations annotations = getSubject().getAnnotations();
34-
final T retVal = annotations.getAnnotation(annotationType);
35-
if (retVal != null) return retVal;
36-
37-
final ContainerAnnotationReflection<T, Annotation> containerAnnotationReflection = getContainerAnnotationReflection();
38-
if (containerAnnotationReflection == null) return null;
39-
final Annotation repeatable = annotations.getAnnotation(containerAnnotationReflection.getRepeatable());
40-
if (repeatable == null) return null;
41-
return containerAnnotationReflection.getCollectionStrategy().builder().add(repeatable).get();
40+
if (annotationType.isAnnotationPresent(Inherited.class)) {
41+
// Inherited annotations
42+
final ContainerAnnotationReflection<T, Annotation> containerAnnotationReflection = getContainerAnnotationReflection();
43+
if (containerAnnotationReflection == null) {
44+
final Optional<IElementSubject> subject = HTree.find(getSubject(), IElementSubject::getParents, s -> s.getAnnotations().isAnnotated(annotationType));
45+
if (!subject.isPresent()) return null;
46+
final IJavaAnnotations annotations = subject.get().getAnnotations();
47+
return annotations.getAnnotation(annotationType);
48+
} else {
49+
final Class<Annotation> repeatableAnnotationType = containerAnnotationReflection.getRepeatable();
50+
final List<Annotation> repeatableAnnotationValues = HTree.dfs(getSubject(), IElementSubject::getParents, false).flatMap(s -> {
51+
final IJavaAnnotations annotations = s.getAnnotations();
52+
final List<Annotation> retVal = new ArrayList<>();
53+
54+
final T annotation = annotations.getAnnotation(annotationType);
55+
if (annotation != null) retVal.addAll(HCollection.asListIterable(containerAnnotationReflection.getCollectionStrategy().iterable(annotation)));
56+
57+
final Annotation repeatable = annotations.getAnnotation(repeatableAnnotationType);
58+
if (repeatable != null) retVal.add(repeatable);
59+
60+
return retVal.stream();
61+
}).collect(Collectors.toList());
62+
return containerAnnotationReflection.getCollectionStrategy().builder().add(repeatableAnnotationValues).get();
63+
}
64+
} else {
65+
// Non-inherited annotations present on the subject
66+
final IJavaAnnotations annotations = getSubject().getAnnotations();
67+
final T retVal = annotations.getAnnotation(annotationType);
68+
if (retVal != null) return retVal;
69+
70+
// Non-inherited annotations which are repeated, and whose repeatable child is on the subject
71+
final ContainerAnnotationReflection<T, Annotation> containerAnnotationReflection = getContainerAnnotationReflection();
72+
if (containerAnnotationReflection == null) return null;
73+
final Annotation repeatable = annotations.getAnnotation(containerAnnotationReflection.getRepeatable());
74+
if (repeatable == null) return null;
75+
return containerAnnotationReflection.getCollectionStrategy().builder().add(repeatable).get();
76+
}
4277
}
4378

4479
@Override
4580
public boolean isPresent() {
46-
final IJavaAnnotations annotations = getSubject().getAnnotations();
47-
final boolean retVal = annotations.isAnnotated(getType().getAnnotationType());
48-
if (retVal) return true;
81+
final Class<T> annotationType = getType().getAnnotationType();
82+
if (annotationType.isAnnotationPresent(Inherited.class)) {
83+
// Inherited annotations
84+
final ContainerAnnotationReflection<T, Annotation> containerAnnotationReflection = getContainerAnnotationReflection();
85+
if (containerAnnotationReflection == null) return HTree.find(getSubject(), IElementSubject::getParents, s -> s.getAnnotations().isAnnotated(annotationType)).isPresent();
86+
else {
87+
final Class<Annotation> repeatableAnnotationType = containerAnnotationReflection.getRepeatable();
88+
return HTree.find(getSubject(), IElementSubject::getParents, s -> {
89+
final IJavaAnnotations annotations = s.getAnnotations();
90+
return annotations.isAnnotated(annotationType) || annotations.isAnnotated(repeatableAnnotationType);
91+
}).isPresent();
92+
}
93+
} else {
94+
final IJavaAnnotations annotations = getSubject().getAnnotations();
95+
final boolean retVal = annotations.isAnnotated(annotationType);
96+
if (retVal) return true;
4997

50-
final ContainerAnnotationReflection<T, Annotation> containerAnnotationReflection = getContainerAnnotationReflection();
51-
if (containerAnnotationReflection == null) return false;
52-
return annotations.isAnnotated(containerAnnotationReflection.getRepeatable());
98+
final ContainerAnnotationReflection<T, Annotation> containerAnnotationReflection = getContainerAnnotationReflection();
99+
if (containerAnnotationReflection == null) return false;
100+
return annotations.isAnnotated(containerAnnotationReflection.getRepeatable());
101+
}
53102
}
54103
}

ha-metadata/src/main/java/com/g2forge/habitat/metadata/value/implementations/ElementSubject.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package com.g2forge.habitat.metadata.value.implementations;
22

33
import java.lang.reflect.AnnotatedElement;
4+
import java.util.ArrayList;
5+
import java.util.List;
46

7+
import com.g2forge.alexandria.java.core.helpers.HCollection;
58
import com.g2forge.alexandria.java.reflect.annotations.ElementJavaAnnotations;
69
import com.g2forge.alexandria.java.reflect.annotations.IJavaAnnotations;
710
import com.g2forge.habitat.metadata.type.subject.ISubjectType;
@@ -19,6 +22,21 @@
1922
@Builder(toBuilder = true)
2023
@RequiredArgsConstructor
2124
class ElementSubject implements IElementSubject {
25+
protected static List<IElementSubject> getParents(IElementSubject _this, AnnotatedElement element) {
26+
if (element instanceof Class) {
27+
@SuppressWarnings("rawtypes")
28+
final Class<?> cast = (Class) element;
29+
30+
final List<IElementSubject> parents = new ArrayList<>();
31+
if (cast.getSuperclass() != null) parents.add(new ElementSubject(_this.getContext(), cast.getSuperclass()));
32+
for (Class<?> parent : cast.getInterfaces()) {
33+
parents.add(new ElementSubject(_this.getContext(), parent));
34+
}
35+
return parents;
36+
}
37+
return HCollection.emptyList();
38+
}
39+
2240
@ToString.Exclude
2341
protected final IMetadataValueContext context;
2442

@@ -33,4 +51,9 @@ class ElementSubject implements IElementSubject {
3351
@EqualsAndHashCode.Exclude
3452
@ToString.Exclude
3553
private final IJavaAnnotations annotations = new ElementJavaAnnotations(getElement());
54+
55+
@Override
56+
public List<IElementSubject> getParents() {
57+
return getParents(this, getElement());
58+
}
3659
}

ha-metadata/src/main/java/com/g2forge/habitat/metadata/value/implementations/ElementValueSubject.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
import java.lang.annotation.Annotation;
44
import java.lang.reflect.AnnotatedElement;
5+
import java.util.List;
56

67
import com.g2forge.alexandria.java.reflect.annotations.ElementJavaAnnotations;
78
import com.g2forge.alexandria.java.reflect.annotations.IJavaAnnotations;
89
import com.g2forge.habitat.metadata.type.subject.IValueSubjectType;
910
import com.g2forge.habitat.metadata.value.IMetadataValueContext;
11+
import com.g2forge.habitat.metadata.value.subject.IElementSubject;
1012
import com.g2forge.habitat.metadata.value.subject.IValueSubject;
1113

1214
import lombok.Builder;
@@ -19,7 +21,7 @@
1921
@Data
2022
@Builder(toBuilder = true)
2123
@RequiredArgsConstructor
22-
public class ElementValueSubject implements IValueSubject {
24+
class ElementValueSubject implements IValueSubject {
2325
@ToString.Exclude
2426
protected final IMetadataValueContext context;
2527

@@ -46,4 +48,9 @@ protected IValueSubjectType computeType() {
4648

4749
return (IValueSubjectType) getContext().getTypeContext().subject(elementType, valueType);
4850
}
51+
52+
@Override
53+
public List<IElementSubject> getParents() {
54+
return ElementSubject.getParents(this, getElement());
55+
}
4956
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package com.g2forge.habitat.metadata.value.subject;
22

33
import java.lang.reflect.AnnotatedElement;
4+
import java.util.List;
45

56
import com.g2forge.alexandria.java.reflect.annotations.IJavaAnnotated;
67
import com.g2forge.habitat.metadata.type.subject.IElementSubjectType;
78

89
@SubjectType(IElementSubjectType.class)
910
public interface IElementSubject extends ISubject, IJavaAnnotated {
1011
public AnnotatedElement getElement();
12+
13+
public List<IElementSubject> getParents();
1114
}
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package com.g2forge.habitat.metadata;
2+
3+
import java.lang.annotation.ElementType;
4+
import java.lang.annotation.Inherited;
5+
import java.lang.annotation.Repeatable;
6+
import java.lang.annotation.Retention;
7+
import java.lang.annotation.RetentionPolicy;
8+
import java.lang.annotation.Target;
9+
import java.util.stream.Collectors;
10+
import java.util.stream.Stream;
11+
12+
import org.junit.Test;
13+
14+
import com.g2forge.alexandria.java.core.helpers.HCollection;
15+
import com.g2forge.alexandria.test.HAssert;
16+
import com.g2forge.habitat.metadata.value.predicate.IPredicate;
17+
18+
/**
19+
* This is similar to {@link TestRepeatableAnnotationMetadata} except that {@link TestInheritedAnnotationMetadata.Contained} is {@link Inherited}.
20+
*/
21+
public class TestInheritedAnnotationMetadata {
22+
@Contained("AC")
23+
@NonRepeatable("ANR")
24+
public interface A {}
25+
26+
@Contained("BC")
27+
@NonRepeatable("BNR")
28+
public interface B extends A {}
29+
30+
public interface C extends A {}
31+
32+
@Retention(RetentionPolicy.RUNTIME)
33+
@Target(ElementType.TYPE)
34+
@Repeatable(Container.class)
35+
@Inherited
36+
public @interface Contained {
37+
public String value();
38+
}
39+
40+
@Retention(RetentionPolicy.RUNTIME)
41+
@Target(ElementType.TYPE)
42+
@Inherited
43+
public @interface Container {
44+
public Contained[] value();
45+
}
46+
47+
@Retention(RetentionPolicy.RUNTIME)
48+
@Target(ElementType.TYPE)
49+
@Inherited
50+
public @interface NonRepeatable {
51+
public String value();
52+
}
53+
54+
@Test
55+
public void aContained() {
56+
final IPredicate<Contained> contained = Metadata.getStandard().of(A.class).bind(Contained.class);
57+
HAssert.assertTrue(contained.isPresent());
58+
HAssert.assertEquals("AC", contained.get0().value());
59+
}
60+
61+
@Test
62+
public void aContainer() {
63+
final IPredicate<Container> container = Metadata.getStandard().of(A.class).bind(Container.class);
64+
HAssert.assertTrue(container.isPresent());
65+
HAssert.assertEquals(HCollection.asList("AC"), Stream.of(container.get0().value()).map(Contained::value).collect(Collectors.toList()));
66+
}
67+
68+
@Test
69+
public void aNonRepeatable() {
70+
final IPredicate<NonRepeatable> nonRepeatable = Metadata.getStandard().of(A.class).bind(NonRepeatable.class);
71+
HAssert.assertTrue(nonRepeatable.isPresent());
72+
HAssert.assertEquals("ANR", nonRepeatable.get0().value());
73+
}
74+
75+
@Test
76+
public void bContained() {
77+
final IPredicate<Contained> contained = Metadata.getStandard().of(B.class).bind(Contained.class);
78+
HAssert.assertTrue(contained.isPresent());
79+
HAssert.assertEquals("BC", contained.get0().value());
80+
}
81+
82+
@Test
83+
public void bContainer() {
84+
final IPredicate<Container> container = Metadata.getStandard().of(B.class).bind(Container.class);
85+
HAssert.assertTrue(container.isPresent());
86+
HAssert.assertEquals(HCollection.asList("BC", "AC"), Stream.of(container.get0().value()).map(Contained::value).collect(Collectors.toList()));
87+
}
88+
89+
@Test
90+
public void bNonRepeatable() {
91+
final IPredicate<NonRepeatable> nonRepeatable = Metadata.getStandard().of(B.class).bind(NonRepeatable.class);
92+
HAssert.assertTrue(nonRepeatable.isPresent());
93+
HAssert.assertEquals("BNR", nonRepeatable.get0().value());
94+
}
95+
96+
@Test
97+
public void cContained() {
98+
final IPredicate<Contained> contained = Metadata.getStandard().of(C.class).bind(Contained.class);
99+
HAssert.assertTrue(contained.isPresent());
100+
HAssert.assertEquals("AC", contained.get0().value());
101+
}
102+
103+
@Test
104+
public void cContainer() {
105+
final IPredicate<Container> container = Metadata.getStandard().of(C.class).bind(Container.class);
106+
HAssert.assertTrue(container.isPresent());
107+
HAssert.assertEquals(HCollection.asList("AC"), Stream.of(container.get0().value()).map(Contained::value).collect(Collectors.toList()));
108+
}
109+
110+
@Test
111+
public void cNonRepeatable() {
112+
final IPredicate<NonRepeatable> nonRepeatable = Metadata.getStandard().of(C.class).bind(NonRepeatable.class);
113+
HAssert.assertTrue(nonRepeatable.isPresent());
114+
HAssert.assertEquals("ANR", nonRepeatable.get0().value());
115+
}
116+
}

0 commit comments

Comments
 (0)