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/03 08:58:01 UTC

[isis] branch master updated: ISIS-2774: simplify Evaluator utilities

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 e13e0a6  ISIS-2774: simplify Evaluator utilities
e13e0a6 is described below

commit e13e0a608f1264cbfe5d56e14b36f583da99d1db
Author: Andi Huber <ah...@apache.org>
AuthorDate: Fri Sep 3 10:57:51 2021 +0200

    ISIS-2774: simplify Evaluator utilities
    
    also has some validation fixes for navigable parent
    
    also simplifies TitleFacet testing
---
 .../isis/applib/spec/AbstractSpecification.java    |   4 +-
 .../isis/applib/spec/AbstractSpecification2.java   |   4 +-
 .../{_MethodCache.java => _ClassCache.java}        |  30 ++-
 .../isis/commons/internal/reflection/_Reflect.java |  60 ++---
 .../isis/core/metamodel/commons/MethodUtil.java    |  11 +-
 .../isis/core/metamodel/facets/Annotations.java    | 242 ---------------------
 .../isis/core/metamodel/facets/Evaluators.java     | 196 +++++++++++++++++
 .../metamodel/facets/FacetFactoryAbstract.java     |   6 +-
 .../navparent/NavigableParentFacetAbstract.java    |   4 +-
 .../NavigableParentAnnotationFacetFactory.java     |  80 ++++---
 ...va => NavigableParentFacetViaGetterMethod.java} |   9 +-
 .../annotation/TitleAnnotationFacetFactory.java    |  49 ++---
 .../annotation/TitleFacetViaTitleAnnotation.java   |  35 ++-
 ...tionEnforcesMetamodelContributionValidator.java |   6 +-
 .../core/metamodel/methods/MethodFinderUtils.java  |   6 +-
 .../classsubstitutor/ClassSubstitutorAbstract.java |   4 +-
 .../specloader/specimpl/FacetedMethodsBuilder.java |   4 +-
 .../TitleAnnotationFacetFactoryTest.java           |  14 +-
 .../TitleFacetViaTitleAnnotationTest.java          |  30 +--
 .../navparent/NavigableParentFacetMethodTest.java  |   6 +-
 .../NavigableParentAnnotationFacetFactoryTest.java |   6 +-
 21 files changed, 389 insertions(+), 417 deletions(-)

diff --git a/api/applib/src/main/java/org/apache/isis/applib/spec/AbstractSpecification.java b/api/applib/src/main/java/org/apache/isis/applib/spec/AbstractSpecification.java
index 27db496..57c4caa 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/spec/AbstractSpecification.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/spec/AbstractSpecification.java
@@ -22,7 +22,7 @@ package org.apache.isis.applib.spec;
 import java.lang.reflect.Method;
 
 import org.apache.isis.applib.annotation.Programmatic;
-import org.apache.isis.commons.internal.reflection._MethodCache;
+import org.apache.isis.commons.internal.reflection._ClassCache;
 
 import lombok.val;
 
@@ -56,7 +56,7 @@ public abstract class AbstractSpecification<T> implements Specification {
 
     private static Class<?> findExpectedType(final Class<?> fromClass) {
 
-        val methodCache = _MethodCache.getInstance();
+        val methodCache = _ClassCache.getInstance();
 
         for (Class<?> c = fromClass; c != Object.class; c = c.getSuperclass()) {
 
diff --git a/api/applib/src/main/java/org/apache/isis/applib/spec/AbstractSpecification2.java b/api/applib/src/main/java/org/apache/isis/applib/spec/AbstractSpecification2.java
index 653e1a3..a1fd2a1 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/spec/AbstractSpecification2.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/spec/AbstractSpecification2.java
@@ -24,7 +24,7 @@ import java.lang.reflect.Method;
 import org.apache.isis.applib.annotation.Programmatic;
 import org.apache.isis.applib.services.i18n.TranslatableString;
 import org.apache.isis.commons.internal.base._Casts;
-import org.apache.isis.commons.internal.reflection._MethodCache;
+import org.apache.isis.commons.internal.reflection._ClassCache;
 
 import lombok.val;
 
@@ -58,7 +58,7 @@ public abstract class AbstractSpecification2<T> implements Specification2 {
 
     private static Class<?> findExpectedType(final Class<?> fromClass) {
 
-        val methodCache = _MethodCache.getInstance();
+        val methodCache = _ClassCache.getInstance();
 
         for (Class<?> c = fromClass; c != Object.class; c = c.getSuperclass()) {
 
diff --git a/commons/src/main/java/org/apache/isis/commons/internal/reflection/_MethodCache.java b/commons/src/main/java/org/apache/isis/commons/internal/reflection/_ClassCache.java
similarity index 87%
rename from commons/src/main/java/org/apache/isis/commons/internal/reflection/_MethodCache.java
rename to commons/src/main/java/org/apache/isis/commons/internal/reflection/_ClassCache.java
index 675d7a6..e587945 100644
--- a/commons/src/main/java/org/apache/isis/commons/internal/reflection/_MethodCache.java
+++ b/commons/src/main/java/org/apache/isis/commons/internal/reflection/_ClassCache.java
@@ -51,10 +51,10 @@ import lombok.val;
  * @since 2.0
  */
 @NoArgsConstructor(access = AccessLevel.PRIVATE)
-public final class _MethodCache implements AutoCloseable {
+public final class _ClassCache implements AutoCloseable {
 
-    public static _MethodCache getInstance() {
-        return _Context.computeIfAbsent(_MethodCache.class, _MethodCache::new);
+    public static _ClassCache getInstance() {
+        return _Context.computeIfAbsent(_ClassCache.class, _ClassCache::new);
     }
 
     public void add(final Class<?> type) {
@@ -83,10 +83,10 @@ public final class _MethodCache implements AutoCloseable {
     }
 
     public Stream<Method> streamPublicOrDeclaredMethods(final Class<?> type) {
-        val methods = inspectType(type);
+        val classModel = inspectType(type);
         return Stream.concat(
-                methods.publicMethodsByKey.values().stream(),
-                methods.nonPublicDeclaredMethodsByKey.values().stream());
+                classModel.publicMethodsByKey.values().stream(),
+                classModel.nonPublicDeclaredMethodsByKey.values().stream());
     }
 
     public Stream<Method> streamDeclaredMethods(final Class<?> type) {
@@ -106,11 +106,11 @@ public final class _MethodCache implements AutoCloseable {
             final String attributeName,
             final Predicate<Method> filter) {
 
-        val methods = inspectType(type);
+        val classModel = inspectType(type);
 
-        synchronized(methods.declaredMethodsByAttribute) {
-            return methods.declaredMethodsByAttribute
-            .computeIfAbsent(attributeName, key->methods.declaredMethods.filter(filter))
+        synchronized(classModel.declaredMethodsByAttribute) {
+            return classModel.declaredMethodsByAttribute
+            .computeIfAbsent(attributeName, key->classModel.declaredMethods.filter(filter))
             .stream();
         }
     }
@@ -118,14 +118,14 @@ public final class _MethodCache implements AutoCloseable {
     // -- IMPLEMENATION DETAILS
 
     @RequiredArgsConstructor
-    private static class Methods {
+    private static class ClassModel {
         private final Map<MethodKey, Method> publicMethodsByKey = new HashMap<>();
         private final Map<MethodKey, Method> nonPublicDeclaredMethodsByKey = new HashMap<>();
         private final Can<Method> declaredMethods;
         private final Map<String, Can<Method>> declaredMethodsByAttribute = new HashMap<>();
     }
 
-    private final Map<Class<?>, Methods> inspectedTypes = new HashMap<>();
+    private final Map<Class<?>, ClassModel> inspectedTypes = new HashMap<>();
 
     @AllArgsConstructor(staticName = "of") @EqualsAndHashCode
     private static final class MethodKey {
@@ -136,10 +136,8 @@ public final class _MethodCache implements AutoCloseable {
         public static MethodKey of(final Class<?> type, final Method method) {
             return MethodKey.of(type, method.getName(), _Arrays.emptyToNull(method.getParameterTypes()));
         }
-
     }
 
-
     @Override
     public void close() throws Exception {
         synchronized(inspectedTypes) {
@@ -149,14 +147,14 @@ public final class _MethodCache implements AutoCloseable {
 
     // -- HELPER
 
-    private Methods inspectType(final Class<?> type) {
+    private ClassModel inspectType(final Class<?> type) {
         synchronized(inspectedTypes) {
 
             return inspectedTypes.computeIfAbsent(type, __->{
 
                 val declaredMethods = type.getDeclaredMethods();
 
-                val methods = new Methods(Can.ofArray(declaredMethods));
+                val methods = new ClassModel(Can.ofArray(declaredMethods));
 
                 for(val method : declaredMethods) {
                     methods.nonPublicDeclaredMethodsByKey.put(MethodKey.of(type, method), method);
diff --git a/commons/src/main/java/org/apache/isis/commons/internal/reflection/_Reflect.java b/commons/src/main/java/org/apache/isis/commons/internal/reflection/_Reflect.java
index 9ab17f4..31dec7d 100644
--- a/commons/src/main/java/org/apache/isis/commons/internal/reflection/_Reflect.java
+++ b/commons/src/main/java/org/apache/isis/commons/internal/reflection/_Reflect.java
@@ -34,17 +34,15 @@ import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
 import java.util.Arrays;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
-import java.util.Spliterator;
-import java.util.Spliterators;
-import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
-import java.util.stream.StreamSupport;
 
 import org.springframework.core.annotation.AnnotationUtils;
 import org.springframework.lang.Nullable;
+import org.springframework.util.ClassUtils;
 
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.functional.Result;
@@ -266,52 +264,38 @@ public final class _Reflect {
 
     // -- SUPER CLASSES
 
+    public enum TypeHierarchyPolicy {
+        EXCLUDE,
+        INCLUDE;
+        public boolean isIncludeTypeHierarchy() {
+            return this == TypeHierarchyPolicy.INCLUDE;
+        }
+    }
+
     public enum InterfacePolicy {
-        INCLUDE,
-        EXCLUDE
+        EXCLUDE,
+        INCLUDE;
+        public boolean isIncludeInterfaces() {
+            return this == InterfacePolicy.INCLUDE;
+        }
     }
 
     /**
      * Stream all types of given {@code type}, up the super class hierarchy starting with self
      * @param type (nullable)
-     * @param interfacePolicy - whether to include all interfaces implemented by given {@code type}.
+     * @param interfacePolicy - whether to include all interfaces implemented by given {@code type} at the end
      * @return non-null
      */
     public static Stream<Class<?>> streamTypeHierarchy(
             final @Nullable Class<?> type,
-            final InterfacePolicy interfacePolicy) {
-
-        val includeInterfaces = interfacePolicy == InterfacePolicy.INCLUDE;
-
-        // https://stackoverflow.com/questions/40240450/java8-streaming-a-class-hierarchy?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
-        // Java 9+ will allow ...
-        // return Stream.iterate(type, Objects::nonNull, Class::getSuperclass);
-
-        return StreamSupport.stream(
-                new Spliterators.AbstractSpliterator<Class<?>>(Long.MAX_VALUE,
-                        Spliterator.ORDERED|Spliterator.IMMUTABLE|Spliterator.NONNULL) {
-                    Class<?> current = type;
-                    @Override
-                    public boolean tryAdvance(final Consumer<? super Class<?>> action) {
-                        if(current == null) return false;
-                        action.accept(current);
-                        if(includeInterfaces) {
-                            for(Class<?> subIface : current.getInterfaces()) {
-                                recur(subIface, action);
-                            }
-                        }
-                        current = current.getSuperclass();
-                        return true;
-                    }
+            final @NonNull InterfacePolicy interfacePolicy) {
 
-                    private void recur(final Class<?> iface, final Consumer<? super Class<?>> action) {
-                        action.accept(iface);
-                        for(Class<?> subIface : iface.getInterfaces()) {
-                            recur(subIface, action);
-                        }
-                    }
+        return interfacePolicy.isIncludeInterfaces()
+                ? Stream.concat(
+                        Stream.<Class<?>>iterate(type, Objects::nonNull, Class::getSuperclass),
+                        ClassUtils.getAllInterfacesForClassAsSet(type).stream())
+                : Stream.iterate(type, Objects::nonNull, Class::getSuperclass);
 
-                }, false);
     }
 
     // -- ANNOTATIONS
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/commons/MethodUtil.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/commons/MethodUtil.java
index cbe22fb..2373263 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/commons/MethodUtil.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/commons/MethodUtil.java
@@ -47,11 +47,16 @@ public class MethodUtil {
         return Modifier.isStatic(modifiers);
     }
 
-    public static boolean isPublic(Member method) {
+    public static boolean isPublic(final Member method) {
         final int modifiers = method.getModifiers();
         return Modifier.isPublic(modifiers);
     }
 
+    public static boolean isNoArg(final Method method) {
+        return method.getParameterCount() == 0;
+    }
+
+
     @UtilityClass
     public static class Predicates {
 
@@ -148,7 +153,7 @@ public class MethodUtil {
          * @return whether the method under test matches the given constraints
          */
         public static Predicate<Method> prefixed(
-                String prefix, Class<?> returnType, CanBeVoid canBeVoid, int paramCount) {
+                final String prefix, final Class<?> returnType, final CanBeVoid canBeVoid, final int paramCount) {
 
             return method -> {
 
@@ -172,7 +177,7 @@ public class MethodUtil {
 
         }
 
-        public static Predicate<Method> getter(Class<?> returnType) {
+        public static Predicate<Method> getter(final Class<?> returnType) {
             return prefixed(MethodLiteralConstants.GET_PREFIX, returnType, CanBeVoid.FALSE, 0);
         }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Annotations.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Annotations.java
deleted file mode 100644
index cb210ab..0000000
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Annotations.java
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one
- *  or more contributor license agreements.  See the NOTICE file
- *  distributed with this work for additional information
- *  regarding copyright ownership.  The ASF licenses this file
- *  to you under the Apache License, Version 2.0 (the
- *  "License"); you may not use this file except in compliance
- *  with the License.  You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing,
- *  software distributed under the License is distributed on an
- *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- *  KIND, either express or implied.  See the License for the
- *  specific language governing permissions and limitations
- *  under the License.
- */
-
-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.Field;
-import java.lang.reflect.Method;
-import java.util.List;
-import java.util.Optional;
-import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-import org.apache.isis.applib.exceptions.unrecoverable.MetaModelException;
-import org.apache.isis.commons.internal.collections._Lists;
-import org.apache.isis.commons.internal.reflection._MethodCache;
-import org.apache.isis.commons.internal.reflection._Reflect;
-import org.apache.isis.core.metamodel.commons.MethodUtil;
-import org.apache.isis.core.metamodel.commons.ThrowableExtensions;
-
-import lombok.Getter;
-import lombok.val;
-import lombok.experimental.UtilityClass;
-import lombok.extern.log4j.Log4j2;
-
-@UtilityClass
-@Log4j2
-public final class Annotations  {
-
-
-    /**
-     * Searches for all no-arg methods or fields with a specified title, returning an
-     * {@link Evaluator} object that wraps either. Will search up hierarchy also,
-     * including implemented interfaces.
-     */
-    public static <T extends Annotation> List<Evaluator<T>> getEvaluators(
-            final Class<?> cls,
-            final Class<T> annotationClass) {
-        final List<Evaluator<T>> evaluators = _Lists.newArrayList();
-        visitEvaluators(cls, annotationClass, evaluators::add);
-
-        // search implemented interfaces
-        final Class<?>[] interfaces = cls.getInterfaces();
-        for (final Class<?> iface : interfaces) {
-            visitEvaluators(iface, annotationClass, evaluators::add);
-        }
-
-        return evaluators;
-    }
-
-    /**
-     * Starting from the current class {@code cls}, we search down the inheritance
-     * hierarchy (super class, super super class, ...), until we find
-     * the first class that has at least a field or no-arg method with {@code annotationClass} annotation.
-     * <br/>
-     * In this hierarchy traversal, implemented interfaces are not processed.
-     * @param cls
-     * @param annotationClass
-     * @param filter
-     * @return list of {@link Evaluator} that wraps each annotated member found on the class where
-     * the search stopped, or an empty list if no such {@code annotationClass} annotation found.
-     *
-     * @since 2.0
-     */
-    public static <T extends Annotation> List<Evaluator<T>> firstEvaluatorsInHierarchyHaving(
-            final Class<?> cls,
-            final Class<T> annotationClass,
-            final Predicate<Evaluator<T>> filter) {
-
-        final List<Evaluator<T>> evaluators = _Lists.newArrayList();
-        visitEvaluatorsWhile(cls, annotationClass, __->evaluators.isEmpty(), evaluator->{
-            if(filter.test(evaluator)) {
-                evaluators.add(evaluator);
-            }
-        });
-
-        return evaluators;
-    }
-
-    private static <T extends Annotation> void visitEvaluators(
-            final Class<?> cls,
-            final Class<T> annotationClass,
-            final Consumer<Evaluator<T>> visitor) {
-        visitEvaluatorsWhile(cls, annotationClass, __->true, visitor);
-    }
-
-    private static <T extends Annotation> void visitEvaluatorsWhile(
-            final Class<?> cls,
-            final Class<T> annotationClass,
-            final Predicate<Class<?>> filter,
-            final Consumer<Evaluator<T>> visitor) {
-
-        if(!filter.test(cls))
-            return; // stop visitation
-
-        visitMethodEvaluators(cls, annotationClass, visitor);
-        visitFieldEvaluators(cls, annotationClass, visitor);
-
-        // search super-classes
-        final Class<?> superclass = cls.getSuperclass();
-        if (superclass != null) {
-            visitEvaluatorsWhile(superclass, annotationClass, filter, visitor);
-        }
-
-    }
-
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    private static <T extends Annotation> void visitMethodEvaluators(
-            final Class<?> cls,
-            final Class<T> annotationClass,
-            final Consumer<Evaluator<T>> visitor) {
-
-        val methodCache = _MethodCache.getInstance();
-
-        methodCache
-        .streamDeclaredMethods(cls)
-        .filter(MethodUtil::isNotStatic)
-        .filter(method->method.getParameterTypes().length == 0)
-        .forEach(method->{
-            final Annotation annotation = method.getAnnotation(annotationClass);
-            if(annotation != null) {
-                visitor.accept(new MethodEvaluator(method, annotation));
-            }
-        });
-    }
-
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    private static <T extends Annotation> void visitFieldEvaluators(
-            final Class<?> cls,
-            final Class<T> annotationClass,
-            final Consumer<Evaluator<T>> visitor) {
-
-        for (final Field field: cls.getDeclaredFields()) {
-            final Annotation annotation = field.getAnnotation(annotationClass);
-            if(annotation != null) {
-                visitor.accept(new FieldEvaluator(field, annotation));
-            }
-        }
-    }
-
-    public static abstract class Evaluator<T extends Annotation> {
-        private final T annotation;
-        private MethodHandle mh;
-
-        protected Evaluator(final T annotation) {
-            this.annotation = annotation;
-        }
-
-        public T getAnnotation() {
-            return annotation;
-        }
-
-        protected abstract MethodHandle createMethodHandle() throws IllegalAccessException;
-        protected abstract String name();
-
-        public Object value(final Object obj) {
-            if(mh==null) {
-                try {
-                    mh = createMethodHandle();
-                } catch (IllegalAccessException e) {
-                    throw new MetaModelException("illegal access of " + name(), e);
-                }
-            }
-
-            try {
-                return mh.invoke(obj);
-            } catch (Throwable e) {
-                return ThrowableExtensions.handleInvocationException(e, name());
-            }
-
-        }
-    }
-
-    public static class MethodEvaluator<T extends Annotation> extends Evaluator<T> {
-        @Getter private final Method method;
-
-        MethodEvaluator(final Method method, final T annotation) {
-            super(annotation);
-            this.method = method;
-        }
-
-        @Override
-        protected String name() {
-            return method.getName();
-        }
-
-        @Override
-        protected MethodHandle createMethodHandle() throws IllegalAccessException {
-            return _Reflect.handleOf(method);
-        }
-    }
-
-    public static class FieldEvaluator<T extends Annotation> extends Evaluator<T> {
-        @Getter private final Field field;
-
-        FieldEvaluator(final Field field, final T annotation) {
-            super(annotation);
-            this.field = field;
-        }
-
-        @Override
-        protected String name() {
-            return field.getName();
-        }
-
-        @Override
-        protected MethodHandle createMethodHandle() throws IllegalAccessException {
-            return _Reflect.handleOfGetterOn(field);
-        }
-
-        public Optional<Method> getGetter(final Class<?> originatingClass) {
-            try {
-                return Optional.ofNullable(
-                        _Reflect.getGetter(originatingClass, field.getName())    );
-            } catch (IntrospectionException e) {
-                log.warn("failed reflective introspection on {} field {}",
-                        originatingClass, field.getName(), e);
-            }
-            return Optional.empty();
-        }
-
-    }
-
-}
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
new file mode 100644
index 0000000..2571ed1
--- /dev/null
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/Evaluators.java
@@ -0,0 +1,196 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+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.Field;
+import java.lang.reflect.Method;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+import org.apache.isis.applib.exceptions.unrecoverable.MetaModelException;
+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;
+import org.apache.isis.commons.internal.reflection._Reflect.TypeHierarchyPolicy;
+import org.apache.isis.core.metamodel.commons.MethodUtil;
+import org.apache.isis.core.metamodel.commons.ThrowableExtensions;
+
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.val;
+import lombok.extern.log4j.Log4j2;
+
+@Log4j2
+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(
+            final @NonNull Class<?> cls,
+            final @NonNull Class<T> annotationType,
+            final @NonNull TypeHierarchyPolicy typeHierarchyPolicy,
+            final @NonNull InterfacePolicy interfacePolicy) {
+
+        return typeHierarchyPolicy.isIncludeTypeHierarchy()
+                ? _Reflect
+                    .streamTypeHierarchy(cls, interfacePolicy)
+                    .flatMap(type->streamAnnotatedMemberEvaluators(type, annotationType))
+                : streamAnnotatedMemberEvaluators(cls, annotationType);
+    }
+
+    // -- HELPER
+
+    private static <T extends Annotation> Stream<Evaluator<T>> streamAnnotatedMemberEvaluators(
+            final Class<?> cls,
+            final Class<T> annotationType) {
+
+        return Stream.concat(
+                streamMethodEvaluators(cls, annotationType),
+                streamFieldEvaluators(cls, annotationType));
+    }
+
+    private static <T extends Annotation> Stream<Evaluator<T>> streamMethodEvaluators(
+            final Class<?> cls,
+            final Class<T> annotationType) {
+
+        val methodCache = _ClassCache.getInstance();
+
+        return methodCache
+        .streamDeclaredMethods(cls)
+        .filter(MethodUtil::isNotStatic)
+        .filter(MethodUtil::isNoArg)
+        .map(method->MethodEvaluator.create(method, annotationType))
+        .flatMap(Optional::stream);
+    }
+
+    private static <T extends Annotation> Stream<Evaluator<T>> streamFieldEvaluators(
+            final Class<?> cls,
+            final Class<T> annotationType) {
+
+        return Stream.<Field>of(cls.getDeclaredFields())
+        .map(field->FieldEvaluator.create(field, annotationType))
+        .flatMap(Optional::stream);
+    }
+
+    // -- EVALUATOR
+
+    public static abstract class Evaluator<T extends Annotation> {
+        @Getter private final T annotation;
+        private MethodHandle mh;
+
+        protected Evaluator(final T annotation) {
+            this.annotation = annotation;
+        }
+
+        protected abstract MethodHandle createMethodHandle() throws IllegalAccessException;
+        public abstract String name();
+
+        public Object value(final Object obj) {
+            if(mh==null) {
+                try {
+                    mh = createMethodHandle();
+                } catch (IllegalAccessException e) {
+                    throw new MetaModelException("illegal access of " + name(), e);
+                }
+            }
+
+            try {
+                return mh.invoke(obj);
+            } catch (Throwable e) {
+                return ThrowableExtensions.handleInvocationException(e, name());
+            }
+
+        }
+    }
+
+    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));
+        }
+
+        @Getter private final Method method;
+
+        MethodEvaluator(final Method method, final T annotation) {
+            super(annotation);
+            this.method = method;
+        }
+
+        @Override
+        public String name() {
+            return method.getName() + "()";
+        }
+
+        @Override
+        protected MethodHandle createMethodHandle() throws IllegalAccessException {
+            return _Reflect.handleOf(method);
+        }
+    }
+
+    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));
+        }
+
+        @Getter private final Field field;
+
+        FieldEvaluator(final Field field, final T annotation) {
+            super(annotation);
+            this.field = field;
+        }
+
+        @Override
+        public String name() {
+            return field.getName();
+        }
+
+        @Override
+        protected MethodHandle createMethodHandle() throws IllegalAccessException {
+            return _Reflect.handleOfGetterOn(field);
+        }
+
+        public Optional<Method> getGetter(final Class<?> originatingClass) {
+            try {
+                return Optional.ofNullable(
+                        _Reflect.getGetter(originatingClass, field.getName())    );
+            } catch (IntrospectionException e) {
+                log.warn("failed reflective introspection on {} field {}",
+                        originatingClass, field.getName(), e);
+            }
+            return Optional.empty();
+        }
+
+    }
+
+}
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/FacetFactoryAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/FacetFactoryAbstract.java
index 7236837..f930307 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/FacetFactoryAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/FacetFactoryAbstract.java
@@ -24,7 +24,7 @@ import java.util.Optional;
 import org.springframework.lang.Nullable;
 
 import org.apache.isis.commons.collections.ImmutableEnumSet;
-import org.apache.isis.commons.internal.reflection._MethodCache;
+import org.apache.isis.commons.internal.reflection._ClassCache;
 import org.apache.isis.core.metamodel.context.HasMetaModelContext;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.facetapi.Facet;
@@ -42,14 +42,14 @@ implements FacetFactory, HasMetaModelContext {
 
     @Getter(onMethod_ = {@Override}) private final ImmutableEnumSet<FeatureType> featureTypes;
 
-    @Getter(AccessLevel.PROTECTED) private final _MethodCache methodCache;
+    @Getter(AccessLevel.PROTECTED) private final _ClassCache methodCache;
 
     public FacetFactoryAbstract(
             final MetaModelContext metaModelContext,
             final ImmutableEnumSet<FeatureType> featureTypes) {
         this.metaModelContext = metaModelContext;
         this.featureTypes = featureTypes;
-        this.methodCache = _MethodCache.getInstance();
+        this.methodCache = _ClassCache.getInstance();
     }
 
     @Override
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetAbstract.java
index a66e856..ea51d34 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetAbstract.java
@@ -23,7 +23,9 @@ import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FacetAbstract;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 
-public abstract class NavigableParentFacetAbstract extends FacetAbstract implements NavigableParentFacet {
+public abstract class NavigableParentFacetAbstract
+extends FacetAbstract
+implements NavigableParentFacet {
 
     private static final Class<? extends Facet> type() {
         return NavigableParentFacet.class;
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 d5718eb..e3c789f 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
@@ -20,22 +20,26 @@
 package org.apache.isis.core.metamodel.facets.object.navparent.annotation;
 
 import java.lang.reflect.Method;
-import java.util.List;
+import java.util.Optional;
 
 import javax.inject.Inject;
 
 import org.apache.isis.applib.annotation.PropertyLayout;
-import org.apache.isis.commons.internal.base._NullSafe;
+import org.apache.isis.commons.collections.Can;
+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;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
 import org.apache.isis.core.metamodel.facetapi.MetaModelRefiner;
-import org.apache.isis.core.metamodel.facets.Annotations;
+import org.apache.isis.core.metamodel.facets.Evaluators;
 import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
-import org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetMethod;
+import org.apache.isis.core.metamodel.facets.object.navparent.NavigableParentFacet;
+import org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetViaGetterMethod;
 import org.apache.isis.core.metamodel.progmodel.ProgrammingModel;
 import org.apache.isis.core.metamodel.specloader.validator.ValidationFailure;
 
+import lombok.val;
 import lombok.extern.log4j.Log4j2;
 
 /**
@@ -66,28 +70,29 @@ implements MetaModelRefiner {
         // That's the one we use to
         // resolve the current domain-object's navigable parent.
 
-        final List<Annotations.Evaluator<PropertyLayout>> evaluators =
-                Annotations.firstEvaluatorsInHierarchyHaving(cls, PropertyLayout.class,
-                        NavigableParentAnnotationFacetFactory::isNavigableParentFlagSet);
+        final Optional<Evaluators.Evaluator<PropertyLayout>> evaluators =
+                Evaluators.streamEvaluators(cls,
+                        PropertyLayout.class,
+                        TypeHierarchyPolicy.INCLUDE,
+                        InterfacePolicy.EXCLUDE)
+                .filter(NavigableParentAnnotationFacetFactory::isNavigableParentFlagSet)
+                .findFirst();
 
-        if (_NullSafe.isEmpty(evaluators)) {
+        if (evaluators.isEmpty()) {
             return; // no parent resolvable
-        } else if (evaluators.size()>1) {
-            // code should not be reached, since case should be handled by meta-data validation
-            throw new RuntimeException("unable to determine navigable parent due to ambiguity");
         }
 
-        final Annotations.Evaluator<PropertyLayout> parentEvaluator = evaluators.get(0);
+        final Evaluators.Evaluator<PropertyLayout> parentEvaluator = evaluators.get();
 
         final Method method;
 
         // find method that provides the parent ...
-        if(parentEvaluator instanceof Annotations.MethodEvaluator) {
+        if(parentEvaluator instanceof Evaluators.MethodEvaluator) {
             // we have a @Parent annotated method
-            method = ((Annotations.MethodEvaluator<PropertyLayout>) parentEvaluator).getMethod();
-        } else if(parentEvaluator instanceof Annotations.FieldEvaluator) {
+            method = ((Evaluators.MethodEvaluator<PropertyLayout>) 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 = ((Annotations.FieldEvaluator<PropertyLayout>) parentEvaluator).getGetter(cls).orElse(null);
+            method = ((Evaluators.FieldEvaluator<PropertyLayout>) parentEvaluator).getGetter(cls).orElse(null);
             if(method==null)
                 return; // code should not be reached, since case should be handled by meta-data validation
 
@@ -96,20 +101,20 @@ implements MetaModelRefiner {
         }
 
         try {
-            addFacet(new NavigableParentFacetMethod(method, facetHolder));
+            addFacet(new NavigableParentFacetViaGetterMethod(method, facetHolder));
         } catch (IllegalAccessException e) {
             log.warn("failed to create NavigableParentFacetMethod method:{} holder:{}",
                     method, facetHolder, e);
         }
     }
 
-    private static boolean isNavigableParentFlagSet(final Annotations.Evaluator<PropertyLayout> evaluator){
+    private static boolean isNavigableParentFlagSet(final Evaluators.Evaluator<PropertyLayout> evaluator){
         return evaluator.getAnnotation().navigable().isParent();
     }
 
 
     /**
-     * For detailed behavioral specification see
+     * For detailed behavior see
      * <a href="https://issues.apache.org/jira/browse/ISIS-1816">ISIS-1816</a>.
      */
     @Override
@@ -117,33 +122,46 @@ implements MetaModelRefiner {
 
         programmingModel.addVisitingValidatorSkipManagedBeans(spec->{
 
-            final Class<?> cls = spec.getCorrespondingClass();
+            val cls = spec.getCorrespondingClass();
 
-            final List<Annotations.Evaluator<PropertyLayout>> evaluators =
-                    Annotations.firstEvaluatorsInHierarchyHaving(cls, PropertyLayout.class,
-                            NavigableParentAnnotationFacetFactory::isNavigableParentFlagSet);
+            if(!spec.lookupFacet(NavigableParentFacet.class).isPresent()) {
+                return; // skip check
+            }
+
+            val evaluators =
+                    Evaluators.streamEvaluators(cls,
+                            PropertyLayout.class,
+                            TypeHierarchyPolicy.EXCLUDE,
+                            InterfacePolicy.INCLUDE)
+                    .filter(NavigableParentAnnotationFacetFactory::isNavigableParentFlagSet)
+                    .collect(Can.toCan());
 
-            if (_NullSafe.isEmpty(evaluators)) {
+            if (evaluators.isEmpty()) {
                 return; // no conflict, continue validation processing
-            } else if (evaluators.size()>1) {
+            } else if (evaluators.isCardinalityMultiple()) {
+
+                val conflictingEvaluatorNames = evaluators.map(Evaluators.Evaluator::name).toList();
 
                 ValidationFailure.raiseFormatted(
                         spec,
                         "%s: conflict for determining a strategy for retrieval of (navigable) parent for class, "
-                                + "contains multiple annotations '@%s' having navigable=PARENT, while at most one is allowed.",
+                                + "contains multiple annotations '@%s' having navigable=PARENT, "
+                                + "while at most one is allowed.\n\tConflicting members: %s",
                                 spec.getFeatureIdentifier().getClassName(),
-                                PropertyLayout.class.getName());
+                                PropertyLayout.class.getName(),
+                                conflictingEvaluatorNames.toString()
+                                );
 
                 return; // continue validation processing
             }
 
-            final Annotations.Evaluator<PropertyLayout> parentEvaluator = evaluators.get(0);
+            final Evaluators.Evaluator<PropertyLayout> parentEvaluator = evaluators.getSingletonOrFail();
 
-            if(parentEvaluator instanceof Annotations.FieldEvaluator) {
+            if(parentEvaluator instanceof Evaluators.FieldEvaluator) {
                 // we have a @Parent annotated field (useful if one uses lombok's @Getter on a field)
 
-                final Annotations.FieldEvaluator<PropertyLayout> fieldEvaluator =
-                        (Annotations.FieldEvaluator<PropertyLayout>) parentEvaluator;
+                final Evaluators.FieldEvaluator<PropertyLayout> fieldEvaluator =
+                        (Evaluators.FieldEvaluator<PropertyLayout>) parentEvaluator;
 
                 if(!fieldEvaluator.getGetter(cls).isPresent()) {
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetMethod.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetViaGetterMethod.java
similarity index 85%
rename from core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetMethod.java
rename to core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetViaGetterMethod.java
index e80ca8c..7aa65d9 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetMethod.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/navparent/method/NavigableParentFacetViaGetterMethod.java
@@ -32,17 +32,20 @@ import org.apache.isis.core.metamodel.facets.object.navparent.NavigableParentFac
  * @since 2.0
  *
  */
-public class NavigableParentFacetMethod extends NavigableParentFacetAbstract {
+public class NavigableParentFacetViaGetterMethod
+extends NavigableParentFacetAbstract {
 
     private final MethodHandle methodHandle;
 
-    public NavigableParentFacetMethod(final Method method, final FacetHolder holder) throws IllegalAccessException {
+    public NavigableParentFacetViaGetterMethod(
+            final Method method,
+            final FacetHolder holder) throws IllegalAccessException {
         super(holder);
         this.methodHandle = _Reflect.handleOf(method);
     }
 
     @Override
-    public Object navigableParent(Object object) {
+    public Object navigableParent(final Object object) {
         try {
             return methodHandle.invoke(object);
         } catch (final Throwable ex) {
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/annotation/TitleAnnotationFacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/annotation/TitleAnnotationFacetFactory.java
index 6e614e8..75262e2 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/annotation/TitleAnnotationFacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/facets/object/title/annotation/TitleAnnotationFacetFactory.java
@@ -19,7 +19,6 @@
 
 package org.apache.isis.core.metamodel.facets.object.title.annotation;
 
-import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -30,10 +29,9 @@ import org.apache.isis.applib.annotation.Title;
 import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
-import org.apache.isis.core.metamodel.facetapi.FacetHolder;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
 import org.apache.isis.core.metamodel.facetapi.MetaModelRefiner;
-import org.apache.isis.core.metamodel.facets.Annotations;
+import org.apache.isis.core.metamodel.facets.Evaluators;
 import org.apache.isis.core.metamodel.facets.FacetFactoryAbstract;
 import org.apache.isis.core.metamodel.facets.fallback.FallbackFacetFactory;
 import org.apache.isis.core.metamodel.facets.object.title.TitleFacet;
@@ -41,6 +39,10 @@ import org.apache.isis.core.metamodel.facets.object.title.methods.TitleFacetViaM
 import org.apache.isis.core.metamodel.progmodel.ProgrammingModel;
 import org.apache.isis.core.metamodel.specloader.validator.ValidationFailure;
 
+import lombok.AccessLevel;
+import lombok.Getter;
+import lombok.val;
+
 public class TitleAnnotationFacetFactory
 extends FacetFactoryAbstract
 implements MetaModelRefiner {
@@ -56,35 +58,28 @@ implements MetaModelRefiner {
      */
     @Override
     public void process(final ProcessClassContext processClassContext) {
-        final Class<?> cls = processClassContext.getCls();
-        final FacetHolder facetHolder = processClassContext.getFacetHolder();
-
-        final var evaluators = Annotations.getEvaluators(cls, Title.class);
-        if (evaluators.isEmpty()) {
-            return;
-        }
-
-        sort(evaluators);
-        final var titleComponents =
-                Can.ofCollection(evaluators)
-                .map(TitleFacetViaTitleAnnotation.TitleComponent::of);
+        val cls = processClassContext.getCls();
+        val facetHolder = processClassContext.getFacetHolder();
 
-        addFacet(new TitleFacetViaTitleAnnotation(titleComponents, facetHolder));
+        addFacetIfPresent(TitleFacetViaTitleAnnotation.create(cls, facetHolder));
     }
 
-    public static void sort(final List<Annotations.Evaluator<Title>> evaluators) {
-        Collections.sort(evaluators, new Comparator<Annotations.Evaluator<Title>>() {
-            Comparator<String> comparator = new SequenceComparator();
+    // static comparator memoization
+    @Getter(lazy = true, value = AccessLevel.PACKAGE)
+    private static final Comparator<Evaluators.Evaluator<Title>> sequenceComparator =
+            new Comparator<Evaluators.Evaluator<Title>>() {
+                final Comparator<String> comparator = new SequenceComparator();
+
+                @Override
+                public int compare(final Evaluators.Evaluator<Title> o1, final Evaluators.Evaluator<Title> o2) {
+                    final Title a1 = o1.getAnnotation();
+                    final Title a2 = o2.getAnnotation();
+                    return comparator.compare(a1.sequence(), a2.sequence());
+                }
+            };
 
-            @Override
-            public int compare(final Annotations.Evaluator<Title> o1, final Annotations.Evaluator<Title> o2) {
-                final Title a1 = o1.getAnnotation();
-                final Title a2 = o2.getAnnotation();
-                return comparator.compare(a1.sequence(), a2.sequence());
-            }
-        });
-    }
 
+    @Deprecated //FIXME[ISI-2774] I believe we have sequence comparators already else where (dewey order)
     static class SequenceComparator implements Comparator<String> {
 
         @Override
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 d6fcdbb..f54e30a 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
@@ -21,6 +21,7 @@ package org.apache.isis.core.metamodel.facets.object.title.annotation;
 
 import java.lang.reflect.Method;
 import java.util.List;
+import java.util.Optional;
 import java.util.function.BiConsumer;
 import java.util.function.Predicate;
 
@@ -29,10 +30,13 @@ import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.commons.internal.collections._Lists;
 import org.apache.isis.commons.internal.functions._Predicates;
+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;
-import org.apache.isis.core.metamodel.facets.Annotations;
-import org.apache.isis.core.metamodel.facets.Annotations.MethodEvaluator;
+import org.apache.isis.core.metamodel.facets.Evaluators;
+import org.apache.isis.core.metamodel.facets.Evaluators.MethodEvaluator;
 import org.apache.isis.core.metamodel.facets.ImperativeFacet;
+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;
 
@@ -46,11 +50,30 @@ public class TitleFacetViaTitleAnnotation
 extends TitleFacetAbstract
 implements ImperativeFacet {
 
+    public static Optional<TitleFacet> create(
+            final @NonNull Class<?> cls,
+            final @NonNull FacetHolder holder){
+
+        val titleComponents = Evaluators.streamEvaluators(cls,
+                Title.class,
+                TypeHierarchyPolicy.EXCLUDE,
+                InterfacePolicy.INCLUDE)
+                .sorted(TitleAnnotationFacetFactory.getSequenceComparator())
+                .map(TitleFacetViaTitleAnnotation.TitleComponent::of)
+                .collect(Can.toCan());
+
+        if (titleComponents.isEmpty()) {
+            return Optional.empty();
+        }
+
+        return Optional.of(new TitleFacetViaTitleAnnotation(titleComponents, holder));
+    }
+
     @Getter private final Can<TitleComponent> components;
 
     @Getter(onMethod_ = {@Override}) private final @NonNull Can<Method> methods;
 
-    public TitleFacetViaTitleAnnotation(final Can<TitleComponent> components, final FacetHolder holder) {
+    protected TitleFacetViaTitleAnnotation(final Can<TitleComponent> components, final FacetHolder holder) {
         super(holder);
         this.components = components;
 
@@ -151,7 +174,7 @@ implements ImperativeFacet {
 
     public static class TitleComponent {
 
-        public static TitleComponent of(final Annotations.Evaluator<Title> titleEvaluator) {
+        public static TitleComponent of(final Evaluators.Evaluator<Title> titleEvaluator) {
             final Title annotation = titleEvaluator.getAnnotation();
             final String prepend = annotation != null ? annotation.prepend() : " ";
             final String append = annotation != null ? annotation.append() : "";
@@ -161,13 +184,13 @@ implements ImperativeFacet {
 
         @Getter private final String prepend;
         @Getter private final String append;
-        @Getter private final Annotations.Evaluator<Title> titleEvaluator;
+        @Getter private final Evaluators.Evaluator<Title> titleEvaluator;
         private final int abbreviateTo;
 
         private TitleComponent(
                 final String prepend,
                 final String append,
-                final Annotations.Evaluator<Title> titleEvaluator,
+                final Evaluators.Evaluator<Title> titleEvaluator,
                 final int abbreviateTo) {
             super();
             this.prepend = prepend;
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/methods/DomainIncludeAnnotationEnforcesMetamodelContributionValidator.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/methods/DomainIncludeAnnotationEnforcesMetamodelContributionValidator.java
index d68313a..2a6d572 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/methods/DomainIncludeAnnotationEnforcesMetamodelContributionValidator.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/methods/DomainIncludeAnnotationEnforcesMetamodelContributionValidator.java
@@ -32,7 +32,7 @@ import org.apache.isis.commons.collections.Can;
 import org.apache.isis.commons.internal.collections._Lists;
 import org.apache.isis.commons.internal.collections._Sets;
 import org.apache.isis.commons.internal.reflection._Annotations;
-import org.apache.isis.commons.internal.reflection._MethodCache;
+import org.apache.isis.commons.internal.reflection._ClassCache;
 import org.apache.isis.commons.internal.reflection._Reflect;
 import org.apache.isis.core.metamodel.commons.MethodUtil;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
@@ -54,12 +54,12 @@ import lombok.val;
 public class DomainIncludeAnnotationEnforcesMetamodelContributionValidator
 extends MetaModelVisitingValidatorAbstract {
 
-    private final _MethodCache methodCache;
+    private final _ClassCache methodCache;
 
     @Inject
     public DomainIncludeAnnotationEnforcesMetamodelContributionValidator(final MetaModelContext mmc) {
         super(mmc);
-        this.methodCache = _MethodCache.getInstance();
+        this.methodCache = _ClassCache.getInstance();
     }
 
     @Override
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/methods/MethodFinderUtils.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/methods/MethodFinderUtils.java
index 82575d3..126c7e9 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/methods/MethodFinderUtils.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/methods/MethodFinderUtils.java
@@ -29,7 +29,7 @@ import java.util.stream.Stream;
 
 import org.apache.isis.applib.services.i18n.TranslatableString;
 import org.apache.isis.commons.collections.Can;
-import org.apache.isis.commons.internal.reflection._MethodCache;
+import org.apache.isis.commons.internal.reflection._ClassCache;
 import org.apache.isis.commons.internal.reflection._Reflect;
 import org.apache.isis.core.metamodel.commons.MethodUtil;
 import org.apache.isis.core.metamodel.facetapi.MethodRemover;
@@ -66,7 +66,7 @@ public final class MethodFinderUtils {
             final Class<?> expectedReturnType,
             final Class<?>[] paramTypes) {
 
-        val methodCache = _MethodCache.getInstance();
+        val methodCache = _ClassCache.getInstance();
 
         val method = options.getEncapsulationPolicy().isEncapsulatedMembersSupported()
                 ? methodCache.lookupPublicOrDeclaredMethod(type, name, paramTypes)
@@ -361,7 +361,7 @@ public final class MethodFinderUtils {
     public static Stream<Method> streamMethods(
             final MethodFinderOptions options,
             final Class<?> type) {
-        val methodCache = _MethodCache.getInstance();
+        val methodCache = _ClassCache.getInstance();
         return options.getEncapsulationPolicy().isEncapsulatedMembersSupported()
                 ? methodCache.streamPublicOrDeclaredMethods(type)
                 : methodCache.streamPublicMethods(type)
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/classsubstitutor/ClassSubstitutorAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/classsubstitutor/ClassSubstitutorAbstract.java
index fe36f56..1da9cde 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/classsubstitutor/ClassSubstitutorAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/classsubstitutor/ClassSubstitutorAbstract.java
@@ -25,7 +25,7 @@ import java.util.Set;
 import org.apache.isis.applib.annotation.Programmatic;
 import org.apache.isis.commons.internal.collections._Sets;
 import org.apache.isis.commons.internal.proxy._ProxyFactoryService;
-import org.apache.isis.commons.internal.reflection._MethodCache;
+import org.apache.isis.commons.internal.reflection._ClassCache;
 import org.apache.isis.core.metamodel.commons.ClassUtil;
 
 import lombok.NonNull;
@@ -33,7 +33,7 @@ import lombok.val;
 
 public abstract class ClassSubstitutorAbstract implements ClassSubstitutor {
 
-    private final _MethodCache methodCache = _MethodCache.getInstance();
+    private final _ClassCache methodCache = _ClassCache.getInstance();
 
     @Override
     public final Substitution getSubstitution(@NonNull final Class<?> cls) {
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/FacetedMethodsBuilder.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/FacetedMethodsBuilder.java
index 8b6207d..1ac1895 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/FacetedMethodsBuilder.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/FacetedMethodsBuilder.java
@@ -42,7 +42,7 @@ import org.apache.isis.commons.internal.base._NullSafe;
 import org.apache.isis.commons.internal.collections._Lists;
 import org.apache.isis.commons.internal.collections._Sets;
 import org.apache.isis.commons.internal.reflection._Annotations;
-import org.apache.isis.commons.internal.reflection._MethodCache;
+import org.apache.isis.commons.internal.reflection._ClassCache;
 import org.apache.isis.commons.internal.reflection._Reflect;
 import org.apache.isis.core.metamodel.commons.CanBeVoid;
 import org.apache.isis.core.metamodel.commons.MethodUtil;
@@ -143,7 +143,7 @@ implements HasMetaModelContext {
         this.isMemberAnnotationsRequired =
                 introspectionPolicy().getMemberAnnotationPolicy().isMemberAnnotationsRequired();
 
-        val methodCache = _MethodCache.getInstance();
+        val methodCache = _ClassCache.getInstance();
         val methodsRemaining = introspectionPolicy().getEncapsulationPolicy().isEncapsulatedMembersSupported()
                 ? methodCache.streamPublicOrDeclaredMethods(introspectedClass)
                 : methodCache.streamPublicMethods(introspectedClass);
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 f614d09..6af1bed 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
@@ -36,7 +36,7 @@ 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;
-import org.apache.isis.core.metamodel.facets.Annotations;
+import org.apache.isis.core.metamodel.facets.Evaluators;
 import org.apache.isis.core.metamodel.facets.FacetFactory.ProcessClassContext;
 import org.apache.isis.core.metamodel.facets.object.title.TitleFacet;
 import org.apache.isis.core.metamodel.facets.object.title.annotation.TitleAnnotationFacetFactory;
@@ -86,8 +86,8 @@ extends AbstractFacetFactoryJUnit4TestCase {
 
         final List<Method> titleMethods = Arrays.asList(Customer.class.getMethod("someTitle"));
         for (int i = 0; i < titleMethods.size(); i++) {
-            final Annotations.MethodEvaluator<Title> titleEvaluator =
-                    (Annotations.MethodEvaluator<Title>) titleFacetViaTitleAnnotation.getComponents().getElseFail(i)
+            final Evaluators.MethodEvaluator<Title> titleEvaluator =
+                    (Evaluators.MethodEvaluator<Title>) titleFacetViaTitleAnnotation.getComponents().getElseFail(i)
                     .getTitleEvaluator();
 
             Assert.assertEquals(titleMethods.get(i),
@@ -114,7 +114,7 @@ extends AbstractFacetFactoryJUnit4TestCase {
 
     }
 
-    @Ignore // to re-instate
+    @Ignore //FIXME[ISI-2774] to re-instate
     @Test
     public void testTitleAnnotatedMethodsPickedUpOnClass() throws Exception {
 
@@ -130,8 +130,8 @@ extends AbstractFacetFactoryJUnit4TestCase {
 
         //final List<TitleComponent> components = titleFacetViaTitleAnnotation.getComponents();
         for (int i = 0; i < titleMethods.size(); i++) {
-            final Annotations.MethodEvaluator<Title> titleEvaluator =
-                    (Annotations.MethodEvaluator<Title>) titleFacetViaTitleAnnotation.getComponents().getElseFail(i)
+            final Evaluators.MethodEvaluator<Title> titleEvaluator =
+                    (Evaluators.MethodEvaluator<Title>) titleFacetViaTitleAnnotation.getComponents().getElseFail(i)
                     .getTitleEvaluator();
 
             Assert.assertEquals(titleMethods.get(i),
@@ -206,7 +206,7 @@ extends AbstractFacetFactoryJUnit4TestCase {
 
     }
 
-    @Ignore // to re-instate
+    @Ignore //FIXME[ISI-2774] to re-instate
     @Test
     public void titleAnnotatedMethodsSomeOfWhichReturnNulls() throws Exception {
 
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/ident/title/annotation/TitleFacetViaTitleAnnotationTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/ident/title/annotation/TitleFacetViaTitleAnnotationTest.java
index 0bdde4d..1eb93d4 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/ident/title/annotation/TitleFacetViaTitleAnnotationTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/ident/title/annotation/TitleFacetViaTitleAnnotationTest.java
@@ -16,11 +16,8 @@
  *  specific language governing permissions and limitations
  *  under the License.
  */
-
 package org.apache.isis.core.metamodel.facets.object.ident.title.annotation;
 
-import java.util.List;
-
 import org.jmock.Expectations;
 import org.jmock.Sequence;
 import org.jmock.auto.Mock;
@@ -32,20 +29,15 @@ import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.MatcherAssert.assertThat;
 
 import org.apache.isis.applib.annotation.Title;
-import org.apache.isis.commons.collections.Can;
 import org.apache.isis.core.internaltestsupport.jmocking.JUnitRuleMockery2;
 import org.apache.isis.core.internaltestsupport.jmocking.JUnitRuleMockery2.Mode;
 import org.apache.isis.core.metamodel._testing.MetaModelContext_forTesting;
 import org.apache.isis.core.metamodel.context.MetaModelContext;
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
-import org.apache.isis.core.metamodel.facets.Annotations;
-import org.apache.isis.core.metamodel.facets.object.title.annotation.TitleAnnotationFacetFactory;
 import org.apache.isis.core.metamodel.facets.object.title.annotation.TitleFacetViaTitleAnnotation;
 import org.apache.isis.core.metamodel.objectmanager.ObjectManager;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 
-import lombok.val;
-
 public class TitleFacetViaTitleAnnotationTest {
 
     @Rule
@@ -95,14 +87,12 @@ public class TitleFacetViaTitleAnnotationTest {
 
     @Test
     public void testTitle() throws Exception {
-        final List<Annotations.Evaluator<Title>> evaluatorList = Annotations
-                .getEvaluators(NormalDomainObject.class, Title.class);
 
-        TitleAnnotationFacetFactory.sort(evaluatorList);
+        final TitleFacetViaTitleAnnotation facet =
+                (TitleFacetViaTitleAnnotation) TitleFacetViaTitleAnnotation
+                .create(NormalDomainObject.class, mockFacetHolder)
+                .orElse(null);
 
-        val components = Can.ofCollection(evaluatorList)
-                .map(TitleFacetViaTitleAnnotation.TitleComponent::of);
-        final TitleFacetViaTitleAnnotation facet = new TitleFacetViaTitleAnnotation(components, mockFacetHolder);
         final NormalDomainObject normalPojo = new NormalDomainObject();
         final Sequence sequence = context.sequence("in-title-element-order");
         context.checking(new Expectations() {
@@ -132,13 +122,13 @@ public class TitleFacetViaTitleAnnotationTest {
     @Test
     public void titleThrowsException() {
 
-        final List<Annotations.Evaluator<Title>> evaluators = Annotations
-                .getEvaluators(DomainObjectWithProblemInItsAnnotatedTitleMethod.class, Title.class);
+        final TitleFacetViaTitleAnnotation facet =
+                (TitleFacetViaTitleAnnotation) TitleFacetViaTitleAnnotation
+                .create(DomainObjectWithProblemInItsAnnotatedTitleMethod.class, mockFacetHolder)
+                .orElse(null);
 
-        val components = Can.ofCollection(evaluators)
-                .map(TitleFacetViaTitleAnnotation.TitleComponent::of);
-        final TitleFacetViaTitleAnnotation facet = new TitleFacetViaTitleAnnotation(components, mockFacetHolder);
-        final DomainObjectWithProblemInItsAnnotatedTitleMethod screwedPojo = new DomainObjectWithProblemInItsAnnotatedTitleMethod();
+        final DomainObjectWithProblemInItsAnnotatedTitleMethod screwedPojo =
+                new DomainObjectWithProblemInItsAnnotatedTitleMethod();
         context.checking(new Expectations() {
             {
 
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodTest.java
index 453c08d..0aa0475 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/NavigableParentFacetMethodTest.java
@@ -29,7 +29,7 @@ import org.junit.Before;
 import org.junit.Test;
 
 import org.apache.isis.core.metamodel.facetapi.FacetHolder;
-import org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetMethod;
+import org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetViaGetterMethod;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 
 import static org.hamcrest.CoreMatchers.is;
@@ -40,7 +40,7 @@ public class NavigableParentFacetMethodTest {
 
     private final Mockery mockery = new JUnit4Mockery();
 
-    private NavigableParentFacetMethod facet;
+    private NavigableParentFacetViaGetterMethod facet;
     private FacetHolder mockFacetHolder;
 
     private ManagedObject mockOwningAdapter;
@@ -60,7 +60,7 @@ public class NavigableParentFacetMethodTest {
         mockFacetHolder = mockery.mock(FacetHolder.class);
         mockOwningAdapter = mockery.mock(ManagedObject.class);
         final Method navigableParentMethod = DomainObjectWithProblemInNavigableParentMethod.class.getMethod("parent");
-        facet = new NavigableParentFacetMethod(navigableParentMethod, mockFacetHolder);
+        facet = new NavigableParentFacetViaGetterMethod(navigableParentMethod, mockFacetHolder);
 
         mockery.checking(new Expectations() {
             {
diff --git a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactoryTest.java b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactoryTest.java
index a109572..a5ae96f 100644
--- a/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactoryTest.java
+++ b/core/metamodel/src/test/java/org/apache/isis/core/metamodel/facets/object/navparent/annotation/NavigableParentAnnotationFacetFactoryTest.java
@@ -32,7 +32,7 @@ import org.apache.isis.core.metamodel.facets.AbstractFacetFactoryJUnit4TestCase;
 import org.apache.isis.core.metamodel.facets.FacetFactory.ProcessClassContext;
 import org.apache.isis.core.metamodel.facets.object.navparent.NavigableParentFacet;
 import org.apache.isis.core.metamodel.facets.object.navparent.annotation.NavigableParentTestSamples.DomainObjectA;
-import org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetMethod;
+import org.apache.isis.core.metamodel.facets.object.navparent.method.NavigableParentFacetViaGetterMethod;
 import org.apache.isis.core.metamodel.spec.ManagedObject;
 
 public class NavigableParentAnnotationFacetFactoryTest
@@ -70,9 +70,9 @@ extends AbstractFacetFactoryJUnit4TestCase {
 
         final Facet facet = facetedMethod.getFacet(NavigableParentFacet.class);
         Assert.assertNotNull(facet);
-        Assert.assertTrue(facet instanceof NavigableParentFacetMethod);
+        Assert.assertTrue(facet instanceof NavigableParentFacetViaGetterMethod);
 
-        final NavigableParentFacetMethod navigableParentFacetMethod = (NavigableParentFacetMethod) facet;
+        final NavigableParentFacetViaGetterMethod navigableParentFacetMethod = (NavigableParentFacetViaGetterMethod) facet;
         final Method parentMethod = domainClass.getMethod(parentMethodName);
 
         Assert.assertEquals(