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 2021/09/05 04:53:40 UTC

[isis] branch master updated: ISIS-2774: meta-annotation support for @Title and 'navigable parent'

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

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


The following commit(s) were added to refs/heads/master by this push:
     new 16cb432  ISIS-2774: meta-annotation support for @Title and 'navigable parent'
16cb432 is described below

commit 16cb43202d46727fea597238317c58dc9c698b1c
Author: andi-huber <ah...@apache.org>
AuthorDate: Sun Sep 5 06:53:27 2021 +0200

    ISIS-2774: meta-annotation support for @Title and 'navigable parent'
    
    makes Evaluator more generic
---
 .../isis/core/metamodel/facets/Evaluators.java     | 86 +++++++++-------------
 .../NavigableParentAnnotationFacetFactory.java     | 36 ++++-----
 .../annotation/TitleFacetViaTitleAnnotation.java   | 65 +++++++++-------
 .../TitleAnnotationFacetFactoryTest.java           | 14 ++--
 4 files changed, 99 insertions(+), 102 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Evaluators.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Evaluators.java
index 7f5f3bf..af7ef63 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Evaluators.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Evaluators.java
@@ -22,12 +22,15 @@ package org.apache.isis.core.metamodel.facets;
 import java.beans.IntrospectionException;
 import java.lang.annotation.Annotation;
 import java.lang.invoke.MethodHandle;
+import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.util.Optional;
+import java.util.function.Predicate;
 import java.util.stream.Stream;
 
 import org.apache.isis.applib.exceptions.unrecoverable.MetaModelException;
+import org.apache.isis.commons.internal.base._NullSafe;
 import org.apache.isis.commons.internal.reflection._ClassCache;
 import org.apache.isis.commons.internal.reflection._Reflect;
 import org.apache.isis.commons.internal.reflection._Reflect.InterfacePolicy;
@@ -37,6 +40,7 @@ import org.apache.isis.core.metamodel.commons.ThrowableExtensions;
 
 import lombok.Getter;
 import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
 import lombok.val;
 import lombok.extern.log4j.Log4j2;
 
@@ -47,35 +51,35 @@ public final class Evaluators  {
      * Streams all fields and no-arg methods having a specified annotationType,
      * each wrapped with an {@link Evaluator} object.
      */
-    public static <T extends Annotation> Stream<Evaluator<T>> streamEvaluators(
+    public static <T extends Annotation> Stream<Evaluator> streamEvaluators(
             final @NonNull Class<?> cls,
-            final @NonNull Class<T> annotationType,
+            final @NonNull Predicate<AnnotatedElement> memberFilter,
             final @NonNull TypeHierarchyPolicy typeHierarchyPolicy,
             final @NonNull InterfacePolicy interfacePolicy) {
 
         return typeHierarchyPolicy.isIncludeTypeHierarchy()
                 ? _Reflect
                     .streamTypeHierarchy(cls, interfacePolicy)
-                    .flatMap(type->streamAnnotatedMemberEvaluators(type, annotationType))
-                : streamAnnotatedMemberEvaluators(cls, annotationType);
+                    .flatMap(type->streamAnnotatedMemberEvaluators(type, memberFilter))
+                : streamAnnotatedMemberEvaluators(cls, memberFilter);
     }
 
     // -- HELPER
 
-    private static <T extends Annotation> Stream<Evaluator<T>> streamAnnotatedMemberEvaluators(
+    private static <T extends Annotation> Stream<Evaluator> streamAnnotatedMemberEvaluators(
             final Class<?> cls,
-            final Class<T> annotationType) {
+            final Predicate<AnnotatedElement> memberFilter) {
 
         val classCache = _ClassCache.getInstance();
 
         return Stream.concat(
-                streamMethodEvaluators(cls, annotationType, classCache),
-                streamFieldEvaluators(cls, annotationType, classCache));
+                streamMethodEvaluators(cls, memberFilter, classCache),
+                streamFieldEvaluators(cls, memberFilter, classCache));
     }
 
-    private static <T extends Annotation> Stream<Evaluator<T>> streamMethodEvaluators(
+    private static Stream<Evaluator> streamMethodEvaluators(
             final Class<?> cls,
-            final Class<T> annotationType,
+            final Predicate<AnnotatedElement> memberFilter,
             final _ClassCache classCache) {
 
         return classCache
@@ -83,30 +87,28 @@ public final class Evaluators  {
         .filter(MethodUtil::isNotStatic)
         .filter(MethodUtil::isNoArg)
         .filter(MethodUtil::isNotVoid)
-        .map(method->MethodEvaluator.create(method, annotationType))
-        .flatMap(Optional::stream);
+        .map(method->memberFilter.test(method) ? new MethodEvaluator(cls, method) : null)
+        .filter(_NullSafe::isPresent)
+        .map(Evaluator.class::cast);
     }
 
-    private static <T extends Annotation> Stream<Evaluator<T>> streamFieldEvaluators(
+    private static <T extends Annotation> Stream<Evaluator> streamFieldEvaluators(
             final Class<?> cls,
-            final Class<T> annotationType,
+            final Predicate<AnnotatedElement> memberFilter,
             final _ClassCache classCache) {
 
         return classCache
         .streamDeclaredFields(cls)
-        .map(field->FieldEvaluator.create(field, annotationType))
-        .flatMap(Optional::stream);
+        .map(field->memberFilter.test(field) ? new FieldEvaluator(cls, field) : null)
+        .filter(_NullSafe::isPresent)
+        .map(Evaluator.class::cast);
     }
 
     // -- EVALUATOR
 
-    public static abstract class Evaluator<T extends Annotation> {
-        @Getter private final T annotation;
-        private MethodHandle mh;
+    public static abstract class Evaluator {
 
-        protected Evaluator(final T annotation) {
-            this.annotation = annotation;
-        }
+        private MethodHandle mh;
 
         protected abstract MethodHandle createMethodHandle() throws IllegalAccessException;
         public abstract String name();
@@ -129,23 +131,12 @@ public final class Evaluators  {
         }
     }
 
-    public static class MethodEvaluator<T extends Annotation> extends Evaluator<T> {
-
-        static <T extends Annotation> Optional<MethodEvaluator<T>> create(
-                final Method method,
-                final Class<T> annotationType) {
-
-            return Optional.ofNullable(method.getAnnotation(annotationType))
-                    .map(annot->new MethodEvaluator<>(method, annot));
-        }
+    @RequiredArgsConstructor
+    public static class MethodEvaluator extends Evaluator {
 
+        @Getter private final Class<?> correspondingClass;
         @Getter private final Method method;
 
-        MethodEvaluator(final Method method, final T annotation) {
-            super(annotation);
-            this.method = method;
-        }
-
         @Override
         public String name() {
             return method.getName() + "()";
@@ -157,23 +148,12 @@ public final class Evaluators  {
         }
     }
 
-    public static class FieldEvaluator<T extends Annotation> extends Evaluator<T> {
-
-        static <T extends Annotation> Optional<FieldEvaluator<T>> create(
-                final Field field,
-                final Class<T> annotationType) {
-
-            return Optional.ofNullable(field.getAnnotation(annotationType))
-                    .map(annot->new FieldEvaluator<>(field, annot));
-        }
+    @RequiredArgsConstructor
+    public static class FieldEvaluator extends Evaluator {
 
+        @Getter private final Class<?> correspondingClass;
         @Getter private final Field field;
 
-        FieldEvaluator(final Field field, final T annotation) {
-            super(annotation);
-            this.field = field;
-        }
-
         @Override
         public String name() {
             return field.getName();
@@ -184,13 +164,13 @@ public final class Evaluators  {
             return _Reflect.handleOfGetterOn(field);
         }
 
-        public Optional<Method> getGetter(final Class<?> originatingClass) {
+        public Optional<Method> lookupGetter() {
             try {
                 return Optional.ofNullable(
-                        _Reflect.getGetter(originatingClass, field.getName())    );
+                        _Reflect.getGetter(correspondingClass, field.getName())    );
             } catch (IntrospectionException e) {
                 log.warn("failed reflective introspection on {} field {}",
-                        originatingClass, field.getName(), e);
+                        correspondingClass, field.getName(), e);
             }
             return Optional.empty();
         }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java
index e3c789f..c7f1c00 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactory.java
@@ -19,6 +19,7 @@
 
 package org.apache.isis.core.metamodel.facets.object.navparent.annotation;
 
+import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Method;
 import java.util.Optional;
 
@@ -26,6 +27,7 @@ import javax.inject.Inject;
 
 import org.apache.isis.applib.annotation.PropertyLayout;
 import org.apache.isis.commons.collections.Can;
+import org.apache.isis.commons.internal.reflection._Annotations;
 import org.apache.isis.commons.internal.reflection._Reflect.InterfacePolicy;
 import org.apache.isis.commons.internal.reflection._Reflect.TypeHierarchyPolicy;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
@@ -70,29 +72,28 @@ implements MetaModelRefiner {
         // That's the one we use to
         // resolve the current domain-object's navigable parent.
 
-        final Optional<Evaluators.Evaluator<PropertyLayout>> evaluators =
+        final Optional<Evaluators.Evaluator> evaluators =
                 Evaluators.streamEvaluators(cls,
-                        PropertyLayout.class,
+                        NavigableParentAnnotationFacetFactory::isNavigableParentFlagSet,
                         TypeHierarchyPolicy.INCLUDE,
                         InterfacePolicy.EXCLUDE)
-                .filter(NavigableParentAnnotationFacetFactory::isNavigableParentFlagSet)
                 .findFirst();
 
         if (evaluators.isEmpty()) {
             return; // no parent resolvable
         }
 
-        final Evaluators.Evaluator<PropertyLayout> parentEvaluator = evaluators.get();
+        final Evaluators.Evaluator parentEvaluator = evaluators.get();
 
         final Method method;
 
         // find method that provides the parent ...
         if(parentEvaluator instanceof Evaluators.MethodEvaluator) {
-            // we have a @Parent annotated method
-            method = ((Evaluators.MethodEvaluator<PropertyLayout>) parentEvaluator).getMethod();
+            // we have a 'parent' annotated method
+            method = ((Evaluators.MethodEvaluator) parentEvaluator).getMethod();
         } else if(parentEvaluator instanceof Evaluators.FieldEvaluator) {
-            // we have a @Parent annotated field (useful if one uses lombok's @Getter on a field)
-            method = ((Evaluators.FieldEvaluator<PropertyLayout>) parentEvaluator).getGetter(cls).orElse(null);
+            // we have a 'parent' annotated field (useful if one uses lombok's @Getter on a field)
+            method = ((Evaluators.FieldEvaluator) parentEvaluator).lookupGetter().orElse(null);
             if(method==null)
                 return; // code should not be reached, since case should be handled by meta-data validation
 
@@ -108,11 +109,13 @@ implements MetaModelRefiner {
         }
     }
 
-    private static boolean isNavigableParentFlagSet(final Evaluators.Evaluator<PropertyLayout> evaluator){
-        return evaluator.getAnnotation().navigable().isParent();
+    private static boolean isNavigableParentFlagSet(final AnnotatedElement annotatedElement){
+        return _Annotations
+                .synthesizeInherited(annotatedElement, PropertyLayout.class)
+                .map(propertyLayout->propertyLayout.navigable().isParent())
+                .orElse(false);
     }
 
-
     /**
      * For detailed behavior see
      * <a href="https://issues.apache.org/jira/browse/ISIS-1816">ISIS-1816</a>.
@@ -130,10 +133,9 @@ implements MetaModelRefiner {
 
             val evaluators =
                     Evaluators.streamEvaluators(cls,
-                            PropertyLayout.class,
+                            NavigableParentAnnotationFacetFactory::isNavigableParentFlagSet,
                             TypeHierarchyPolicy.EXCLUDE,
                             InterfacePolicy.INCLUDE)
-                    .filter(NavigableParentAnnotationFacetFactory::isNavigableParentFlagSet)
                     .collect(Can.toCan());
 
             if (evaluators.isEmpty()) {
@@ -155,15 +157,15 @@ implements MetaModelRefiner {
                 return; // continue validation processing
             }
 
-            final Evaluators.Evaluator<PropertyLayout> parentEvaluator = evaluators.getSingletonOrFail();
+            final Evaluators.Evaluator parentEvaluator = evaluators.getSingletonOrFail();
 
             if(parentEvaluator instanceof Evaluators.FieldEvaluator) {
                 // we have a @Parent annotated field (useful if one uses lombok's @Getter on a field)
 
-                final Evaluators.FieldEvaluator<PropertyLayout> fieldEvaluator =
-                        (Evaluators.FieldEvaluator<PropertyLayout>) parentEvaluator;
+                final Evaluators.FieldEvaluator fieldEvaluator =
+                        (Evaluators.FieldEvaluator) parentEvaluator;
 
-                if(!fieldEvaluator.getGetter(cls).isPresent()) {
+                if(!fieldEvaluator.lookupGetter().isPresent()) {
 
                     ValidationFailure.raiseFormatted(
                             spec,
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/annotation/TitleFacetViaTitleAnnotation.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/annotation/TitleFacetViaTitleAnnotation.java
index aa192e8..cc6da87 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/annotation/TitleFacetViaTitleAnnotation.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/annotation/TitleFacetViaTitleAnnotation.java
@@ -19,18 +19,22 @@
 
 package org.apache.isis.core.metamodel.facets.object.title.annotation;
 
+import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Method;
 import java.util.List;
 import java.util.Optional;
 import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 
 import org.apache.isis.applib.annotation.Title;
 import org.apache.isis.commons.collections.Can;
+import org.apache.isis.commons.internal.base._Refs;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.commons.internal.collections._Lists;
 import org.apache.isis.commons.internal.compare._Comparators;
 import org.apache.isis.commons.internal.functions._Predicates;
+import org.apache.isis.commons.internal.reflection._Annotations;
 import org.apache.isis.commons.internal.reflection._Reflect.InterfacePolicy;
 import org.apache.isis.commons.internal.reflection._Reflect.TypeHierarchyPolicy;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
@@ -41,8 +45,10 @@ import org.apache.isis.core.metamodel.facets.object.title.TitleFacet;
 import org.apache.isis.core.metamodel.facets.object.title.TitleFacetAbstract;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 
+import lombok.AccessLevel;
 import lombok.Getter;
 import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
 import lombok.val;
 import lombok.extern.log4j.Log4j2;
 
@@ -55,14 +61,15 @@ implements ImperativeFacet {
             final @NonNull Class<?> cls,
             final @NonNull FacetHolder holder){
 
-        val titleComponents = Evaluators.streamEvaluators(cls,
-                Title.class,
-                TypeHierarchyPolicy.EXCLUDE,
-                InterfacePolicy.INCLUDE)
-                .sorted((eval1, eval2) -> _Comparators.deweyOrderCompare(
-                        eval1.getAnnotation().sequence(),
-                        eval2.getAnnotation().sequence()))
-                .map(TitleFacetViaTitleAnnotation.TitleComponent::of)
+        val titleRef = _Refs.<Title>objectRef(null);
+
+        val titleComponents = Evaluators
+                .streamEvaluators(cls,
+                    annotatedElement->isTitleComponent(annotatedElement, titleRef::set),
+                    TypeHierarchyPolicy.EXCLUDE,
+                    InterfacePolicy.INCLUDE)
+                .map(evaluator->TitleComponent.of(evaluator, titleRef.getValueElseFail()))
+                .sorted()
                 .collect(Can.toCan());
 
         if (titleComponents.isEmpty()) {
@@ -175,33 +182,36 @@ implements ImperativeFacet {
         return str.length() < maxLength ? str : str.substring(0, maxLength - 3) + "...";
     }
 
-    public static class TitleComponent {
+    private static boolean isTitleComponent(
+            final AnnotatedElement annotatedElement,
+            final Consumer<Title> onTitleFound){
+        return _Annotations
+                .synthesizeInherited(annotatedElement, Title.class)
+                .map(title->{onTitleFound.accept(title); return true;})
+                .orElse(false);
+    }
+
+    @RequiredArgsConstructor(access = AccessLevel.PRIVATE)
+    public static class TitleComponent
+    implements Comparable<TitleComponent> {
 
-        public static TitleComponent of(final Evaluators.Evaluator<Title> titleEvaluator) {
-            final Title annotation = titleEvaluator.getAnnotation();
+        public static TitleComponent of(
+                final Evaluators.Evaluator titleEvaluator,
+                final Title annotation) {
+
+            final String deweyOrdinal = annotation != null ? annotation.sequence() : "1";
             final String prepend = annotation != null ? annotation.prepend() : " ";
             final String append = annotation != null ? annotation.append() : "";
             final int abbreviateTo = annotation != null ? annotation.abbreviatedTo() : Integer.MAX_VALUE;
-            return new TitleComponent(prepend, append, titleEvaluator, abbreviateTo);
+            return new TitleComponent(titleEvaluator, deweyOrdinal, prepend, append, abbreviateTo);
         }
 
+        @Getter private final Evaluators.Evaluator titleEvaluator;
+        @Getter private final String deweyOrdinal;
         @Getter private final String prepend;
         @Getter private final String append;
-        @Getter private final Evaluators.Evaluator<Title> titleEvaluator;
         private final int abbreviateTo;
 
-        private TitleComponent(
-                final String prepend,
-                final String append,
-                final Evaluators.Evaluator<Title> titleEvaluator,
-                final int abbreviateTo) {
-            super();
-            this.prepend = prepend;
-            this.append = append;
-            this.titleEvaluator = titleEvaluator;
-            this.abbreviateTo = abbreviateTo;
-        }
-
         @Override
         public String toString() {
             final List<String> parts = _Lists.newArrayList();
@@ -216,5 +226,10 @@ implements ImperativeFacet {
             }
             return String.join(";", parts);
         }
+
+        @Override
+        public int compareTo(final TitleComponent other) {
+            return _Comparators.deweyOrderCompare(this.getDeweyOrdinal(), other.getDeweyOrdinal());
+        }
     }
 }
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/ident/title/annotation/TitleAnnotationFacetFactoryTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/ident/title/annotation/TitleAnnotationFacetFactoryTest.java
index 905dab7..0aacdc4 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/ident/title/annotation/TitleAnnotationFacetFactoryTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/ident/title/annotation/TitleAnnotationFacetFactoryTest.java
@@ -31,9 +31,6 @@ import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
 import org.apache.isis.applib.annotation.Title;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facets.AbstractFacetFactoryJUnit4TestCase;
@@ -45,6 +42,9 @@ import org.apache.isis.core.metamodel.facets.object.title.annotation.TitleFacetV
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
 
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
 public class TitleAnnotationFacetFactoryTest
 extends AbstractFacetFactoryJUnit4TestCase {
 
@@ -90,8 +90,8 @@ extends AbstractFacetFactoryJUnit4TestCase {
 
         final List<Method> titleMethods = Arrays.asList(Customer.class.getMethod("someTitle"));
         for (int i = 0; i < titleMethods.size(); i++) {
-            final Evaluators.MethodEvaluator<Title> titleEvaluator =
-                    (Evaluators.MethodEvaluator<Title>) titleFacetViaTitleAnnotation.getComponents().getElseFail(i)
+            final Evaluators.MethodEvaluator titleEvaluator =
+                    (Evaluators.MethodEvaluator) titleFacetViaTitleAnnotation.getComponents().getElseFail(i)
                     .getTitleEvaluator();
 
             Assert.assertEquals(titleMethods.get(i),
@@ -133,8 +133,8 @@ extends AbstractFacetFactoryJUnit4TestCase {
 
         //final List<TitleComponent> components = titleFacetViaTitleAnnotation.getComponents();
         for (int i = 0; i < titleMethods.size(); i++) {
-            final Evaluators.MethodEvaluator<Title> titleEvaluator =
-                    (Evaluators.MethodEvaluator<Title>) titleFacetViaTitleAnnotation.getComponents().getElseFail(i)
+            final Evaluators.MethodEvaluator titleEvaluator =
+                    (Evaluators.MethodEvaluator) titleFacetViaTitleAnnotation.getComponents().getElseFail(i)
                     .getTitleEvaluator();
 
             Assert.assertEquals(titleMethods.get(i),