You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2019/10/03 21:18:26 UTC

[isis] branch v2 updated: ISIS-2158: refinements to _Annotations to support attribute inheritance

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch v2
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/v2 by this push:
     new 03e666f  ISIS-2158: refinements to _Annotations to support attribute inheritance
03e666f is described below

commit 03e666f7f145c225ff031b2e00b51fe9c14d569b
Author: Andi Huber <ah...@apache.org>
AuthorDate: Thu Oct 3 23:18:17 2019 +0200

    ISIS-2158: refinements to _Annotations to support attribute inheritance
---
 .../commons/internal/reflection/_Annotations.java  | 108 +++++++++++++++++++--
 .../Annotations_getAnnotations_on_Class_Test.java  |  78 ++++++++++++---
 2 files changed, 162 insertions(+), 24 deletions(-)

diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/reflection/_Annotations.java b/core/commons/src/main/java/org/apache/isis/commons/internal/reflection/_Annotations.java
index 1c6bb27..a74241a 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/reflection/_Annotations.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/reflection/_Annotations.java
@@ -25,6 +25,8 @@ import org.springframework.core.annotation.MergedAnnotation;
 import org.springframework.core.annotation.MergedAnnotations;
 import org.springframework.core.annotation.MergedAnnotations.SearchStrategy;
 
+import org.apache.isis.commons.internal.base._Strings;
+
 import lombok.AccessLevel;
 import lombok.NoArgsConstructor;
 import lombok.val;
@@ -42,6 +44,9 @@ public final class _Annotations {
     
     /**
      * Optionally create a type-safe synthesized version of this annotation based on presence.
+     * <p>
+     * Does not support attribute inheritance.
+     * 
      * @param <A>
      * @param annotatedElement
      * @param annotationType
@@ -52,25 +57,112 @@ public final class _Annotations {
             Class<A> annotationType) {
         
         val synthesized = _Annotations
-                .merge(annotatedElement, annotationType)
-                .synthesize(ma->ma.isPresent());
+                .collect(annotatedElement)
+                .get(annotationType)
+                .synthesize(MergedAnnotation::isPresent);
         
         return synthesized;
     }
+    
+    // -- ATTRIBUTE FETCHERS
+    
+    /**
+     * Optionally create a String from attribute values based on presence.
+     * @param <A>
+     * @param attributeName
+     * @param annotatedElement
+     * @param annotationType
+     * @param attributeAcceptStrategy
+     * @return non-null
+     */
+    public static <A extends Annotation> Optional<String> getString(
+            String attributeName,
+            Class<?> annotatedElement, 
+            Class<A> annotationType,
+            AttributeAcceptStrategy attributeAcceptStrategy) {
+        
+        val value = _Annotations
+                .collect(annotatedElement)
+                .stream(annotationType)
+                .map(ma->ma.getString(attributeName))
+                .filter(attributeAcceptStrategy::acceptString)
+                .findFirst();
+        
+        return value;
+    }
 
+    /**
+     * Optionally create an Enum from attribute values based on presence.
+     * @param <A>
+     * @param <E>
+     * @param attributeName
+     * @param annotatedElement
+     * @param annotationType
+     * @param enumType
+     * @param attributeAcceptStrategy
+     * @return non-null
+     */
+    public static <A extends Annotation, E extends Enum<E>> Optional<E> getEnum(
+            String attributeName,
+            Class<?> annotatedElement, 
+            Class<A> annotationType,
+            Class<E> enumType, 
+            AttributeAcceptStrategy attributeAcceptStrategy) {
+        
+        val value = _Annotations
+                .collect(annotatedElement)
+                .stream(annotationType)
+                .map(ma->ma.getEnum(attributeName, enumType))
+                .filter(attributeAcceptStrategy::acceptEnum)
+                .findFirst();
+        
+        return value;
+    }
+    
+    // -- SHORTCUTS
+    
+    public static <A extends Annotation> Optional<String> getString(
+            String attributeName,
+            Class<?> annotatedElement, 
+            Class<A> annotationType) {
+        return getString(attributeName, annotatedElement, annotationType, DEFAULT_ATTRIBUTE_ACCEPT_STRATEGY);
+    }
+    
+    public static <A extends Annotation, E extends Enum<E>> Optional<E> getEnum(
+            String attributeName,
+            Class<?> annotatedElement, 
+            Class<A> annotationType,
+            Class<E> enumType) {
+        return getEnum(attributeName, annotatedElement, annotationType, enumType, DEFAULT_ATTRIBUTE_ACCEPT_STRATEGY);
+    }   
+
+    // -- BEHAVIOR
+    
+    public final static AttributeAcceptStrategy DEFAULT_ATTRIBUTE_ACCEPT_STRATEGY = 
+            new AttributeAcceptStrategy() {};
+    
+    static interface AttributeAcceptStrategy {
+        
+        default boolean acceptString(String value) {
+            return !_Strings.isNullOrEmpty(value);
+        }
+        
+        default boolean acceptEnum(Enum<?> value) {
+            return value != null && !value.name().equals("NOT_SPECIFIED"); 
+        }
+        
+    }
+    
     // -- HELPER
     
     /**
      * @apiNote don't expose Spring's MergedAnnotation
      */
-    private static <A extends Annotation> MergedAnnotation<A> merge(
-            Class<?> annotatedElement, 
-            Class<A> annotationType) {
+    private static MergedAnnotations collect(Class<?> annotatedElement) {
         
         //TODO use cache
-        val merged = MergedAnnotations.from(annotatedElement, SearchStrategy.SUPERCLASS);
-        
-        return merged.get(annotationType);
+        val collected = MergedAnnotations.from(annotatedElement, SearchStrategy.INHERITED_ANNOTATIONS);
+        return collected;
     }
     
     
diff --git a/core/metamodel/src/test/java/org/apache/isis/metamodel/facets/Annotations_getAnnotations_on_Class_Test.java b/core/metamodel/src/test/java/org/apache/isis/metamodel/facets/Annotations_getAnnotations_on_Class_Test.java
index 1987051..f60646b 100644
--- a/core/metamodel/src/test/java/org/apache/isis/metamodel/facets/Annotations_getAnnotations_on_Class_Test.java
+++ b/core/metamodel/src/test/java/org/apache/isis/metamodel/facets/Annotations_getAnnotations_on_Class_Test.java
@@ -24,12 +24,13 @@ import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.lang.annotation.Target;
 
-import org.junit.Assert;
-import org.junit.Test;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.Test;
 
 import org.apache.isis.commons.internal.reflection._Annotations;
 
 import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
 
 import lombok.val;
 
@@ -46,6 +47,7 @@ public class Annotations_getAnnotations_on_Class_Test {
             NOT_SPECIFIED
         }
         Publishng publishng() default Publishng.NOT_SPECIFIED;
+        String name() default "";
     }
 
 
@@ -64,6 +66,13 @@ public class Annotations_getAnnotations_on_Class_Test {
     @Retention(RetentionPolicy.RUNTIME)
     @interface NotPublished {
     }
+    
+    @DomainObj(name = "foo")
+    @Inherited
+    @Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
+    @Retention(RetentionPolicy.RUNTIME)
+    @interface NamedFoo {
+    }
 
     //@Meta
     @Published
@@ -82,8 +91,8 @@ public class Annotations_getAnnotations_on_Class_Test {
         val synthesized = _Annotations
                 .synthesize(SomeDomainObject.class, DomainObj.class);
         
-        Assert.assertThat(synthesized.isPresent(), is(true));
-        Assert.assertThat(synthesized.get().publishng(), is(DomainObj.Publishng.YES));
+        assertThat(synthesized.isPresent(), is(true));
+        assertThat(synthesized.get().publishng(), is(DomainObj.Publishng.YES));
     }
 
     @Test
@@ -95,8 +104,8 @@ public class Annotations_getAnnotations_on_Class_Test {
         val synthesized = _Annotations
                 .synthesize(SomeDomainObject.class, DomainObj.class);
 
-        Assert.assertThat(synthesized.isPresent(), is(true));
-        Assert.assertThat(synthesized.get().publishng(), is(DomainObj.Publishng.YES));
+        assertThat(synthesized.isPresent(), is(true));
+        assertThat(synthesized.get().publishng(), is(DomainObj.Publishng.YES));
     }
 
     @Test
@@ -108,8 +117,8 @@ public class Annotations_getAnnotations_on_Class_Test {
         val synthesized = _Annotations
                 .synthesize(SomeDomainObject.class, DomainObj.class);
 
-        Assert.assertThat(synthesized.isPresent(), is(true));
-        Assert.assertThat(synthesized.get().publishng(), is(DomainObj.Publishng.YES));
+        assertThat(synthesized.isPresent(), is(true));
+        assertThat(synthesized.get().publishng(), is(DomainObj.Publishng.YES));
     }
 
     @Test
@@ -122,8 +131,8 @@ public class Annotations_getAnnotations_on_Class_Test {
         val synthesized = _Annotations
                 .synthesize(SomeDomainObject.class, DomainObj.class);
 
-        Assert.assertThat(synthesized.isPresent(), is(true));
-        Assert.assertThat(synthesized.get().publishng(), is(DomainObj.Publishng.YES));
+        assertThat(synthesized.isPresent(), is(true));
+        assertThat(synthesized.get().publishng(), is(DomainObj.Publishng.YES));
     }
 
     @Test
@@ -136,8 +145,8 @@ public class Annotations_getAnnotations_on_Class_Test {
         val synthesized = _Annotations
                 .synthesize(SomeDomainObject.class, DomainObj.class);
 
-        Assert.assertThat(synthesized.isPresent(), is(true));
-        Assert.assertThat(synthesized.get().publishng(), is(DomainObj.Publishng.NO));
+        assertThat(synthesized.isPresent(), is(true));
+        assertThat(synthesized.get().publishng(), is(DomainObj.Publishng.NO));
     }
 
     @Test
@@ -151,8 +160,8 @@ public class Annotations_getAnnotations_on_Class_Test {
         val synthesized = _Annotations
                 .synthesize(SomeDomainObject.class, DomainObj.class);
 
-        Assert.assertThat(synthesized.isPresent(), is(true));
-        Assert.assertThat(synthesized.get().publishng(), is(DomainObj.Publishng.NO));
+        assertThat(synthesized.isPresent(), is(true));
+        assertThat(synthesized.get().publishng(), is(DomainObj.Publishng.NO));
     }
 
 
@@ -167,8 +176,45 @@ public class Annotations_getAnnotations_on_Class_Test {
         val synthesized = _Annotations
                 .synthesize(SomeDomainObject.class, DomainObj.class);
 
-        Assert.assertThat(synthesized.isPresent(), is(true));
-        Assert.assertThat(synthesized.get().publishng(), is(DomainObj.Publishng.YES));
+        assertThat(synthesized.isPresent(), is(true));
+        assertThat(synthesized.get().publishng(), is(DomainObj.Publishng.YES));
+    }
+    
+    @Test
+    public void merge_only_valid_attributes() throws Exception {
+
+        @NamedFoo // <-- should provide name=foo even if the below matches first on publishng=YES
+        @DomainObj(publishng = DomainObj.Publishng.YES) // <-- should provide YES
+        class SomeDomainObject {}
+
+        val publishng = _Annotations
+                .getEnum("publishng", SomeDomainObject.class, DomainObj.class, DomainObj.Publishng.class);
+        
+        val name = _Annotations
+                .getString("name", SomeDomainObject.class, DomainObj.class);
+
+        assertThat(publishng.isPresent(), is(true));
+        assertThat(name.isPresent(), is(true));
+        
+        assertThat(publishng.get(), is(DomainObj.Publishng.YES));
+        assertThat(name.get(), is("foo"));
+    }
+    
+    @Test @Disabled("unfortunately there is no easy way to plug into synthesizing, "
+            + "to get the desired behavior")
+    public void ignore_not_specified() throws Exception {
+
+        @MetaPublished
+        @NotPublished // <-- should win over the above
+        @DomainObj(publishng = DomainObj.Publishng.NOT_SPECIFIED) // <-- should be ignored
+        class SomeDomainObject {}
+
+        val synthesized = _Annotations
+                .synthesize(SomeDomainObject.class, DomainObj.class);
+        
+        assertThat(synthesized.isPresent(), is(true));
+        assertThat(synthesized.get().publishng(), is(DomainObj.Publishng.NO));
     }
+    
 
 }
\ No newline at end of file