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

[isis] branch v2 updated: ISIS-1998: proper mixin main method detection

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

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


The following commit(s) were added to refs/heads/v2 by this push:
     new 61cfbf3  ISIS-1998: proper mixin main method detection
61cfbf3 is described below

commit 61cfbf31ab8bfe467475f2ea896464d2d5a1dff6
Author: Andi Huber <ah...@apache.org>
AuthorDate: Tue Oct 8 16:52:43 2019 +0200

    ISIS-1998: proper mixin main method detection
    
    - doing this now in a single place: FacetedMethodsBuilder
    - solves issue with any public mixin method being picked up as
    contributing action
---
 .../presets/DebugProgrammingModel.properties       |   1 +
 .../apache/isis/metamodel/facets/FacetFactory.java | 151 +++++++++++----------
 .../specloader/facetprocessor/FacetProcessor.java  |  22 ++-
 .../specloader/specimpl/FacetedMethodsBuilder.java | 112 ++++++++++-----
 4 files changed, 169 insertions(+), 117 deletions(-)

diff --git a/core/config/src/main/resources/presets/DebugProgrammingModel.properties b/core/config/src/main/resources/presets/DebugProgrammingModel.properties
index 07313c8..ce27d89 100644
--- a/core/config/src/main/resources/presets/DebugProgrammingModel.properties
+++ b/core/config/src/main/resources/presets/DebugProgrammingModel.properties
@@ -16,3 +16,4 @@
 #  under the License.
 
 logging.level.org.apache.isis.metamodel.specloader.ProgrammingModelServiceDefault = DEBUG
+logging.level.org.apache.isis.metamodel.specloader.specimpl.FacetedMethodsBuilder = DEBUG
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/FacetFactory.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/FacetFactory.java
index cb6dc45..968bd9c 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/FacetFactory.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/facets/FacetFactory.java
@@ -27,19 +27,15 @@ import java.util.Optional;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
-import org.apache.isis.commons.internal.base._Lazy;
 import org.apache.isis.commons.internal.exceptions._Exceptions;
 import org.apache.isis.commons.internal.reflection._Annotations;
-import org.apache.isis.metamodel.MetaModelContext;
 import org.apache.isis.metamodel.facetapi.Facet;
 import org.apache.isis.metamodel.facetapi.FacetHolder;
 import org.apache.isis.metamodel.facetapi.FeatureType;
 import org.apache.isis.metamodel.facetapi.MethodRemover;
 import org.apache.isis.metamodel.methodutils.MethodScope;
-import org.apache.isis.metamodel.specloader.specimpl.IntrospectionState;
-import org.apache.isis.metamodel.specloader.specimpl.ObjectActionMixedIn;
 
-import lombok.val;
+import lombok.Getter;
 
 public interface FacetFactory {
 
@@ -55,7 +51,8 @@ public interface FacetFactory {
         }
     }
 
-    static class AbstractProcessWithClsContext<T extends FacetHolder> extends AbstractProcessContext<T>{
+    static class AbstractProcessWithClsContext<T extends FacetHolder>
+    extends AbstractProcessContext<T>{
 
         private final Class<?> cls;
 
@@ -81,12 +78,18 @@ public interface FacetFactory {
         
     }
 
-    static class AbstractProcessWithMethodContext<T extends FacetHolder> extends AbstractProcessWithClsContext<T> implements MethodRemover{
+    static class AbstractProcessWithMethodContext<T extends FacetHolder> 
+    extends AbstractProcessWithClsContext<T> implements MethodRemover{
 
         private final Method method;
         protected final MethodRemover methodRemover;
 
-        AbstractProcessWithMethodContext(final Class<?> cls, final Method method, final MethodRemover methodRemover, final T facetHolder) {
+        AbstractProcessWithMethodContext(
+                final Class<?> cls, 
+                final Method method, 
+                final MethodRemover methodRemover, 
+                final T facetHolder) {
+            
             super(cls, facetHolder);
             this.method = method;
             this.methodRemover = methodRemover;
@@ -123,64 +126,6 @@ public interface FacetFactory {
         public void removeMethod(final Method method) {
             methodRemover.removeMethod(method);
         }
-        
-        /** 
-         * Annotation lookup on this context's method..
-         * @since 2.0
-         */
-        public <A extends Annotation> Optional<A> synthesizeOnMethod(Class<A> annotationType) {
-            return _Annotations.synthesizeInherited(method, annotationType);
-        }
-        
-        /** 
-         * Annotation lookup on this context's method, if not found, extends search to type in case 
-         * the predicate {@link #isMixinMain} evaluates {@code true}.
-         * @since 2.0
-         */
-        public <A extends Annotation> Optional<A> synthesizeOnMethodOrMixinType(Class<A> annotationType) {
-            return computeIfAbsent(synthesizeOnMethod(annotationType),
-                    ()-> isMixinMain()
-                        ? synthesizeOnType(annotationType)
-                                : Optional.empty() ) ;
-        }
-        
-        /**
-         * Whether we are currently processing a mixin type AND this context's method can be identified 
-         * as the main method of the processed mixin class.
-         * @since 2.0
-         */
-        public boolean isMixinMain() {
-            return isMixinMain.get();
-        }
-
-        private final _Lazy<Boolean> isMixinMain = _Lazy.threadSafe(this::computeIsMixinMain);
-        
-        private boolean computeIsMixinMain() {
-            val specLoader = MetaModelContext.current().getSpecificationLoader();
-            val spec_ofTypeCurrentlyProcessing = specLoader
-                    .loadSpecification(getCls(), IntrospectionState.TYPE_INTROSPECTED);
-            if(!spec_ofTypeCurrentlyProcessing.isMixin()) {
-                return false;
-            }
-            val mixinMember = spec_ofTypeCurrentlyProcessing
-                    .getMixedInMember(spec_ofTypeCurrentlyProcessing);
-            if(!mixinMember.isPresent()) {
-                return false;
-            }
-            val actionMethod_ofMixinMember = ((ObjectActionMixedIn)mixinMember.get())
-                    .getFacetedMethod().getMethod();
-            
-            return getMethod().equals(actionMethod_ofMixinMember);
-        }
-        
-        private static <T> Optional<T> computeIfAbsent(
-                Optional<T> optional,
-                Supplier<Optional<T>> supplier) {
-            
-            return optional.isPresent() ? 
-                    optional
-                        : supplier.get();
-        }
 
     }
 
@@ -205,7 +150,10 @@ public interface FacetFactory {
     // process class
     // //////////////////////////////////////
 
-    public static class ProcessClassContext extends AbstractProcessWithClsContext<FacetHolder> implements MethodRemover, ProcessContextWithMetadataProperties<FacetHolder> {
+    public static class ProcessClassContext 
+    extends AbstractProcessWithClsContext<FacetHolder> 
+    implements MethodRemover, ProcessContextWithMetadataProperties<FacetHolder> {
+        
         private final MethodRemover methodRemover;
 
         /**
@@ -244,23 +192,78 @@ public interface FacetFactory {
     // //////////////////////////////////////
 
 
-    public static class ProcessMethodContext extends AbstractProcessWithMethodContext<FacetedMethod> implements  ProcessContextWithMetadataProperties<FacetedMethod> {
-        private final FeatureType featureType;
+    public static class ProcessMethodContext 
+    extends AbstractProcessWithMethodContext<FacetedMethod> 
+    implements ProcessContextWithMetadataProperties<FacetedMethod> {
+        
+        @Getter private final FeatureType featureType;
+        @Getter private final boolean mixinMain;
 
+        /**
+         * 
+         * @param cls
+         * @param featureType
+         * @param method
+         * @param methodRemover
+         * @param facetedMethod
+         * @param isMixinMain
+         *       - Whether we are currently processing a mixin type AND this context's method can be identified 
+         *         as the main method of the processed mixin class. (since 2.0)
+         */
         public ProcessMethodContext(
                 final Class<?> cls,
                 final FeatureType featureType,
                 final Method method,
                 final MethodRemover methodRemover,
-                final FacetedMethod facetedMethod) {
+                final FacetedMethod facetedMethod,
+                final boolean isMixinMain) {
+            
             super(cls, method, methodRemover, facetedMethod);
             this.featureType = featureType;
+            this.mixinMain = false;
+        }
+        
+        /** JUnit support, historically not using 'isMixinMain' */
+        public ProcessMethodContext(
+                final Class<?> cls,
+                final FeatureType featureType,
+                final Method method,
+                final MethodRemover methodRemover,
+                final FacetedMethod facetedMethod) {
+            this(cls, featureType, method, methodRemover, facetedMethod, false);
         }
 
-        public FeatureType getFeatureType() {
-            return featureType;
+        
+        /** 
+         * Annotation lookup on this context's method..
+         * @since 2.0
+         */
+        public <A extends Annotation> Optional<A> synthesizeOnMethod(Class<A> annotationType) {
+            return _Annotations.synthesizeInherited(getMethod(), annotationType);
         }
         
+        /** 
+         * Annotation lookup on this context's method, if not found, extends search to type in case 
+         * the predicate {@link #isMixinMain} evaluates {@code true}.
+         * @since 2.0
+         */
+        public <A extends Annotation> Optional<A> synthesizeOnMethodOrMixinType(Class<A> annotationType) {
+            return computeIfAbsent(synthesizeOnMethod(annotationType),
+                    ()-> isMixinMain()
+                        ? synthesizeOnType(annotationType)
+                                : Optional.empty() ) ;
+        }
+        
+        private static <T> Optional<T> computeIfAbsent(
+                Optional<T> optional,
+                Supplier<Optional<T>> supplier) {
+            
+            return optional.isPresent() ? 
+                    optional
+                        : supplier.get();
+        }
+
+        
     }
 
     /**
@@ -274,7 +277,9 @@ public interface FacetFactory {
     // process param
     // //////////////////////////////////////
 
-    public static class ProcessParameterContext extends AbstractProcessWithMethodContext<FacetedMethodParameter> {
+    public static class ProcessParameterContext 
+    extends AbstractProcessWithMethodContext<FacetedMethodParameter> {
+        
         private final int paramNum;
         private final Class<?> paramType;
         private final Parameter parameter; 
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/facetprocessor/FacetProcessor.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/facetprocessor/FacetProcessor.java
index 4207784..4185e20 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/facetprocessor/FacetProcessor.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/facetprocessor/FacetProcessor.java
@@ -25,6 +25,7 @@ import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Consumer;
 
 import org.apache.isis.applib.services.inject.ServiceInjector;
 import org.apache.isis.commons.internal.base._Lazy;
@@ -166,9 +167,9 @@ public class FacetProcessor {
      * Delegates to all known
      * {@link PropertyOrCollectionIdentifyingFacetFactory}s.
      */
-    public Set<Method> findAssociationCandidateAccessors(
+    public void findAssociationCandidateAccessors(
             Collection<Method> methods, 
-            Set<Method> candidates) {
+            Consumer<Method> onCandidate) {
         
         val factories = propertyOrCollectionIdentifyingFactories.get();
         
@@ -178,11 +179,11 @@ public class FacetProcessor {
             }
             for (val facetFactory : factories) {
                 if (facetFactory.isPropertyOrCollectionAccessorCandidate(method)) {
-                    candidates.add(method);
+                    onCandidate.accept(method);
                 }
             }
         }
-        return candidates;
+        
     }
 
     /**
@@ -330,17 +331,24 @@ public class FacetProcessor {
      * @param featureType
      *            - what type of feature the method represents (property,
      *            action, collection etc)
+     * @param isMixinMain
+     *            - Whether we are currently processing a mixin type AND this context's method 
+     *            can be identified as the main method of the processed mixin class. (since 2.0)
      */
     public void process(
             Class<?> cls,
             Method method,
             MethodRemover methodRemover,
             FacetedMethod facetedMethod,
-            FeatureType featureType) {
-        
+            FeatureType featureType, 
+            boolean isMixinMain) {
         
         val processMethodContext =
-                new ProcessMethodContext(cls, featureType, method, removerElseNoopRemover(methodRemover), facetedMethod);
+                new ProcessMethodContext(
+                        cls, 
+                        featureType, 
+                        method, 
+                        removerElseNoopRemover(methodRemover), facetedMethod, isMixinMain);
         
         factoryListByFeatureType.get().getOrElseEmpty(featureType)
         .forEach(facetFactory->facetFactory.process(processMethodContext));
diff --git a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/FacetedMethodsBuilder.java b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/FacetedMethodsBuilder.java
index 195f09e..4fa4b8a 100644
--- a/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/FacetedMethodsBuilder.java
+++ b/core/metamodel/src/main/java/org/apache/isis/metamodel/specloader/specimpl/FacetedMethodsBuilder.java
@@ -150,15 +150,15 @@ public class FacetedMethodsBuilder {
     // ////////////////////////////////////////////////////////////////////////////
 
     public FacetedMethodsBuilder(
-            final ObjectSpecificationAbstract spec,
+            final ObjectSpecificationAbstract inspectedTypeSpec,
             final FacetedMethodsBuilderContext facetedMethodsBuilderContext) {
         
         if (log.isDebugEnabled()) {
-            log.debug("creating JavaIntrospector for {}", spec.getFullIdentifier());
+            log.debug("creating JavaIntrospector for {}", inspectedTypeSpec.getFullIdentifier());
         }
 
-        this.inspectedTypeSpec = spec;
-        this.introspectedClass = spec.getCorrespondingClass();
+        this.inspectedTypeSpec = inspectedTypeSpec;
+        this.introspectedClass = inspectedTypeSpec.getCorrespondingClass();
         
         val methodsRemaining = introspectedClass.getMethods();
         this.methodRemover = new FacetedMethodsMethodRemover(introspectedClass, methodsRemaining);
@@ -240,11 +240,11 @@ public class FacetedMethodsBuilder {
             log.debug("introspecting {}: properties and collections", getClassName());
         }
         
-        final Set<Method> associationCandidateMethods = new HashSet<Method>();
+        val associationCandidateMethods = new HashSet<Method>();
         
         methodRemover.acceptRemaining(methodsRemaining->{
             getFacetProcessor()
-            .findAssociationCandidateAccessors(methodsRemaining, associationCandidateMethods);
+            .findAssociationCandidateAccessors(methodsRemaining, associationCandidateMethods::add);
         });
 
         // Ensure all return types are known
@@ -259,45 +259,52 @@ public class FacetedMethodsBuilder {
         typesToLoad.forEach(typeToLoad->specLoader.loadSpecification(typeToLoad, upTo));
 
         // now create FacetedMethods for collections and for properties
-        final List<FacetedMethod> associationFacetedMethods = _Lists.newArrayList();
+        val associationFacetedMethods = _Lists.<FacetedMethod>newArrayList();
 
-        findAndRemoveCollectionAccessorsAndCreateCorrespondingFacetedMethods(associationFacetedMethods);
-        findAndRemovePropertyAccessorsAndCreateCorrespondingFacetedMethods(associationFacetedMethods);
+        findAndRemoveCollectionAccessorsAndCreateCorrespondingFacetedMethods(associationFacetedMethods::add);
+        findAndRemovePropertyAccessorsAndCreateCorrespondingFacetedMethods(associationFacetedMethods::add);
 
         return Collections.unmodifiableList(associationFacetedMethods);
     }
 
-    private void findAndRemoveCollectionAccessorsAndCreateCorrespondingFacetedMethods(final List<FacetedMethod> associationPeers) {
-        final List<Method> collectionAccessors = _Lists.newArrayList();
+    private void findAndRemoveCollectionAccessorsAndCreateCorrespondingFacetedMethods(Consumer<FacetedMethod> onNewAssociationPeer) {
+        val collectionAccessors = _Lists.<Method>newArrayList();
         getFacetProcessor().findAndRemoveCollectionAccessors(methodRemover, collectionAccessors);
-        createCollectionFacetedMethodsFromAccessors(collectionAccessors, associationPeers);
+        createCollectionFacetedMethodsFromAccessors(collectionAccessors, onNewAssociationPeer);
     }
 
     /**
      * Since the value properties and collections have already been processed,
      * this will pick up the remaining reference properties.
      */
-    private void findAndRemovePropertyAccessorsAndCreateCorrespondingFacetedMethods(final List<FacetedMethod> fields) {
-        final List<Method> propertyAccessors = _Lists.newArrayList();
+    private void findAndRemovePropertyAccessorsAndCreateCorrespondingFacetedMethods(Consumer<FacetedMethod> onNewField) {
+        val propertyAccessors = _Lists.<Method>newArrayList();
         getFacetProcessor().findAndRemovePropertyAccessors(methodRemover, propertyAccessors);
 
-        findAndRemovePrefixedNonVoidMethods(MethodScope.OBJECT, GET_PREFIX, Object.class, 0, propertyAccessors);
-        findAndRemovePrefixedNonVoidMethods(MethodScope.OBJECT, IS_PREFIX, Boolean.class, 0, propertyAccessors);
+        findAndRemovePrefixedNonVoidMethods(MethodScope.OBJECT, GET_PREFIX, Object.class, 0, propertyAccessors::add);
+        findAndRemovePrefixedNonVoidMethods(MethodScope.OBJECT, IS_PREFIX, Boolean.class, 0, propertyAccessors::add);
 
-        createPropertyFacetedMethodsFromAccessors(propertyAccessors, fields);
+        createPropertyFacetedMethodsFromAccessors(propertyAccessors, onNewField);
     }
 
     private void createCollectionFacetedMethodsFromAccessors(
             final List<Method> accessorMethods,
-            final List<FacetedMethod> facetMethodsToAppendto) {
+            final Consumer<FacetedMethod> onNewFacetMethod) {
+        
         for (final Method accessorMethod : accessorMethods) {
             if (log.isDebugEnabled()) {
                 log.debug("  identified accessor method representing collection: {}", accessorMethod);
             }
 
             // create property and add facets
-            final FacetedMethod facetedMethod = FacetedMethod.createForCollection(introspectedClass, accessorMethod);
-            getFacetProcessor().process(introspectedClass, accessorMethod, methodRemover, facetedMethod, FeatureType.COLLECTION);
+            val facetedMethod = FacetedMethod.createForCollection(introspectedClass, accessorMethod);
+            getFacetProcessor().process(
+                    introspectedClass, 
+                    accessorMethod, 
+                    methodRemover, 
+                    facetedMethod, 
+                    FeatureType.COLLECTION,
+                    isMixinMain(accessorMethod));
 
             // figure out what the type is
             Class<?> elementType = Object.class;
@@ -312,13 +319,13 @@ public class FacetedMethodsBuilder {
                 continue;
             }
 
-            facetMethodsToAppendto.add(facetedMethod);
+            onNewFacetMethod.accept(facetedMethod);
         }
     }
 
     private void createPropertyFacetedMethodsFromAccessors(
             final List<Method> accessorMethods,
-            final List<FacetedMethod> facetedMethodsToAppendto) throws MetaModelException {
+            final Consumer<FacetedMethod> onNewFacetedMethod) throws MetaModelException {
 
         for (final Method accessorMethod : accessorMethods) {
             log.debug("  identified accessor method representing property: {}", accessorMethod);
@@ -331,12 +338,18 @@ public class FacetedMethodsBuilder {
             }
 
             // create a 1:1 association peer
-            final FacetedMethod facetedMethod = FacetedMethod.createForProperty(introspectedClass, accessorMethod);
-
-            // process facets for the 1:1 association
-            getFacetProcessor().process(introspectedClass, accessorMethod, methodRemover, facetedMethod, FeatureType.PROPERTY);
-
-            facetedMethodsToAppendto.add(facetedMethod);
+            val facetedMethod = FacetedMethod.createForProperty(introspectedClass, accessorMethod);
+
+            // process facets for the 1:1 association (eg. contributed properties)
+            getFacetProcessor().process(
+                    introspectedClass, 
+                    accessorMethod, 
+                    methodRemover, 
+                    facetedMethod, 
+                    FeatureType.PROPERTY,
+                    isMixinMain(accessorMethod));
+
+            onNewFacetedMethod.accept(facetedMethod);
         }
     }
 
@@ -407,7 +420,13 @@ public class FacetedMethodsBuilder {
         final FacetedMethod action = FacetedMethod.createForAction(introspectedClass, actionMethod);
 
         // process facets on the action & parameters
-        getFacetProcessor().process(introspectedClass, actionMethod, methodRemover, action, FeatureType.ACTION);
+        getFacetProcessor().process(
+                introspectedClass, 
+                actionMethod, 
+                methodRemover, 
+                action, 
+                FeatureType.ACTION,
+                isMixinMain(actionMethod));
 
         final List<FacetedMethodParameter> actionParams = action.getParameters();
         for (int j = 0; j < actionParams.size(); j++) {
@@ -418,8 +437,8 @@ public class FacetedMethodsBuilder {
     }
 
     private boolean isAllParamTypesValid(final Method actionMethod) {
-        for (final Class<?> paramType : actionMethod.getParameterTypes()) {
-            final ObjectSpecification paramSpec = getSpecificationLoader().loadSpecification(paramType);
+        for (val paramType : actionMethod.getParameterTypes()) {
+            val paramSpec = getSpecificationLoader().loadSpecification(paramType);
             if (paramSpec == null) {
                 return false;
             }
@@ -453,16 +472,16 @@ public class FacetedMethodsBuilder {
             return false;
         }
         
-        if(this.inspectedTypeSpec.isMixin()) {
+        if(isMixinMain(actionMethod)) {
             // we are introspecting a mixin type, so accept this method for further processing
-            log.debug("  identified possible mixin´s 'main' action {0}", actionMethod);
+            log.debug("  identified mixin-main action {}", actionMethod);
             return true;
         } 
         
         if(explicitActionAnnotationConfigured()) {
             
             if(_Annotations.isPresent(actionMethod, Action.class)) {
-                log.debug("  identified action {0}", actionMethod);
+                log.debug("  identified action {}", actionMethod);
                 return true;
             }
             // we have no @Action, so dismiss
@@ -476,7 +495,7 @@ public class FacetedMethodsBuilder {
         }
         // we have a valid action candidate, so fall through
         
-        log.debug("  identified action {0}", actionMethod);
+        log.debug("  identified action {}", actionMethod);
         return true;
     }
 
@@ -515,9 +534,9 @@ public class FacetedMethodsBuilder {
             final String prefix,
             final Class<?> returnType,
             final int paramCount,
-            final List<Method> methodListToAppendTo) {
+            final Consumer<Method> onRemoved) {
         
-        findAndRemovePrefixedMethods(methodScope, prefix, returnType, false, paramCount, methodListToAppendTo::add);
+        findAndRemovePrefixedMethods(methodScope, prefix, returnType, false, paramCount, onRemoved);
     }
 
     /**
@@ -538,6 +557,25 @@ public class FacetedMethodsBuilder {
         });
         
     }
+    
+    /**
+     * In case this inspected type is a mixin, returns whether given method can be identified 
+     * as this mixin's main method. 
+     *  
+     * @param method
+     */
+    private boolean isMixinMain(Method method) {
+        if(!this.inspectedTypeSpec.isMixin()) {
+            return false;
+        }
+        val mixinMember = inspectedTypeSpec.getMixedInMember(inspectedTypeSpec);
+        if(!mixinMember.isPresent()) {
+            return false;
+        }
+        val actionMethod_ofMixinMember = ((ObjectActionMixedIn)mixinMember.get())
+                .getFacetedMethod().getMethod();
+        return method.equals(actionMethod_ofMixinMember);
+    }
 
     // ////////////////////////////////////////////////////////////////////////////
     // toString