record FacetProcessor()

in core/metamodel/src/main/java/org/apache/causeway/core/metamodel/spec/impl/FacetProcessor.java [59:405]


record FacetProcessor(
        ProgrammingModel programmingModel,
        /**
         * {@link FacetFactory Facet factories}, in order as provided by the ProgrammingModel.
         */
        Can<FacetFactory> factories,
        /**
         * Class<FacetFactory> => FacetFactory
         */
        Map<Class<? extends FacetFactory>, FacetFactory> factoryByFactoryType,
        /**
         * <pre>ObjectFeatureType => List of FacetFactory</pre>
         * <p>
         * The lists remain in the same order as the order in
         * {@link #factories}.
         */
        ListMultimap<FeatureType, FacetFactory> factoriesByFeatureType,
        /**
         * All method prefixes to check in {@link #recognizes(Method)}.
         * <p>
         * Derived from factories that implement
         * {@link MethodPrefixBasedFacetFactory}.
         */
        Set<String> methodPrefixes,
        /**
         * All registered {@link FacetFactory factories} that implement
         * {@link MethodFilteringFacetFactory}.
         */
        List<MethodFilteringFacetFactory> methodFilteringFactories,
        /**
         * {@link FacetFactory factories} that implement {@link AccessorFacetFactory}
         * and support properties.
         */
        List<AccessorFacetFactory> propertyAccessorFactories,
        /**
         * {@link FacetFactory factories} that implement {@link AccessorFacetFactory}
         * and support collections.
         */
        List<AccessorFacetFactory> collectionAccessorFactories,
        List<ObjectTypeFacetFactory> objectSpecIfFacetFactoryList
        )
implements HasMetaModelContext {

    public FacetProcessor(
            final @NonNull ProgrammingModel programmingModel) {
        this(programmingModel, programmingModel.streamFactories()
                .map(programmingModel.getMetaModelContext().getServiceInjector()::injectServicesInto)
                .collect(Can.toCan()));
    }

    private FacetProcessor(
            final ProgrammingModel programmingModel,
            final Can<FacetFactory> factories) {
        this(programmingModel, factories, factories.toMap(FacetFactory::getClass), factoriesByFeatureType(factories));
    }

    private FacetProcessor(
            final ProgrammingModel programmingModel,
            final Can<FacetFactory> factories,
            final Map<Class<? extends FacetFactory>, FacetFactory> factoryByFactoryType,
            final ListMultimap<FeatureType, FacetFactory> factoriesByFeatureType) {
        this(programmingModel,
                factories,
                factoryByFactoryType,
                factoriesByFeatureType,
                methodPrefixes(factories),
                methodFilteringFactories(factories),
                propertyAccessorFactories(factories),
                collectionAccessorFactories(factories),
                objectSpecIfFacetFactoryList(factoriesByFeatureType, factories));
    }

    @Override
    public MetaModelContext getMetaModelContext() {
        return programmingModel.getMetaModelContext();
    }

    /**
     * Appends to the supplied {@link Set} all of the {@link Method}s that may
     * represent a property or collection.
     * <p>
     * Delegates to all known
     * {@link AccessorFacetFactory}s.
     */
    public void findAssociationCandidateGetters(
            final Stream<ResolvedMethod> methodStream,
            final Consumer<ResolvedMethod> onCandidate) {

        methodStream.forEach(method->{
            for (var facetFactory : propertyAccessorFactories) {
                if (facetFactory.isAssociationAccessor(method)) {
                    onCandidate.accept(method);
                    return; // first wins
                }
            }
            for (var facetFactory : collectionAccessorFactories) {
                if (facetFactory.isAssociationAccessor(method)) {
                    onCandidate.accept(method);
                    return; // first wins
                }
            }
        });
    }

    /**
     * Use the provided {@link MethodRemover} to call all known
     * {@link AccessorFacetFactory}s to remove all
     * property accessors and append them to the supplied methodList.
     */
    public List<ResolvedMethod> findAndRemovePropertyAccessors(
            final MethodRemover methodRemover) {
        var propertyAccessors = new ArrayList<ResolvedMethod>();
        for (var facetFactory : propertyAccessorFactories) {
            methodRemover.removeMethods(facetFactory::isAssociationAccessor, propertyAccessors::add);
        }
        return propertyAccessors;
    }

    /**
     * Use the provided {@link MethodRemover} to call all known
     * {@link AccessorFacetFactory}s to remove all
     * collection accessors and append them to the supplied methodList.
     */
    public List<ResolvedMethod> findAndRemoveCollectionAccessors(
            final MethodRemover methodRemover) {
        var collectionAccessors = new ArrayList<ResolvedMethod>();
        for (var facetFactory : collectionAccessorFactories) {
            methodRemover.removeMethods(facetFactory::isAssociationAccessor, collectionAccessors::add);
        }
        return collectionAccessors;
    }

    /**
     * Whether this {@link Method method} is recognized by any of the
     * {@link FacetFactory}s.
     * <p>
     * Typically this is when method has a specific prefix, such as
     * <tt>validate</tt> or <tt>hide</tt>. Specifically, it checks:
     * <ul>
     * <li>the method's prefix against the prefixes supplied by any
     * {@link MethodPrefixBasedFacetFactory}</li>
     * <li>the method against any {@link MethodFilteringFacetFactory}</li>
     * </ul>
     * <p>
     * The design of {@link MethodPrefixBasedFacetFactory} (whereby this facet
     * factory set does the work) is a slight performance optimization for when
     * there are multiple facet factories that search for the same prefix.
     */
    public boolean recognizes(final ResolvedMethod method) {
        var methodName = method.name();
        for (var prefix : methodPrefixes) {
            if (methodName.startsWith(prefix)) return true;
        }
        for (var factory : methodFilteringFactories) {
            if (factory.recognizes(method)) return true;
        }
        return false;
    }

    public void processObjectType(final Class<?> cls, final FacetHolder facetHolder) {
        for (var facetFactory : objectSpecIfFacetFactoryList()) {
            facetFactory.process(new ProcessObjectTypeContext(cls, facetHolder));
        }
    }

    /**
     * Attaches all facets applicable to the provided {@link FeatureType#OBJECT
     * object}) to the supplied {@link FacetHolder}.
     * <p>
     * Delegates to {@link FacetFactory#process(FacetFactory.ProcessClassContext)} for each
     * appropriate factory.
     *
     * @see FacetFactory#process(ProcessClassContext)
     *
     * @param cls
     *            - class to process
     * @param facetHolder
     *            - holder to attach facets to.
     */
    public void process(
            final Class<?> cls,
            final IntrospectionPolicy introspectionPolicy,
            final MethodRemover methodRemover,
            final FacetHolder facetHolder) {
        var ctx = new ProcessClassContext(
                cls,
                introspectionPolicy,
                removerElseNoopRemover(methodRemover),
                facetHolder);

        factoriesByFeatureType.getOrElseEmpty(FeatureType.OBJECT)
            .forEach(facetFactory->facetFactory.process(ctx));
    }

    /**
     * Attaches all facets applicable to the provided {@link FeatureType type of
     * feature} to the supplied {@link FacetHolder}.
     * <p>
     * Delegates to {@link FacetFactory#process(FacetFactory.ProcessMethodContext)} for each
     * appropriate factory.
     *
     * @param cls
     *            - class in which introspect; allowing the helper methods to be
     *            found is subclasses of that which the method was originally
     *            found.
     * @param method
     *            - method to process
     * @param facetedMethod
     *            - holder to attach facets to.
     * @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(
            final Class<?> cls,
            final IntrospectionPolicy introspectionPolicy,
            final MethodFacade method,
            final MethodRemover methodRemover,
            final FacetedMethod facetedMethod,
            final FeatureType featureType,
            final boolean isMixinMain) {

        var processMethodContext =
                new ProcessMethodContext(
                        cls,
                        introspectionPolicy,
                        featureType,
                        method,
                        removerElseNoopRemover(methodRemover), facetedMethod, isMixinMain);

        for (FacetFactory facetFactory : factoriesByFeatureType.getOrElseEmpty(featureType)) {
            facetFactory.process(processMethodContext);
        }
    }

    public void processMemberOrder(final ObjectMember facetHolder) {

    }

    /**
     * Attaches all facets applicable to the provided parameter to the supplied
     * {@link FacetHolder}.
     * <p>
     * Delegates to {@link FacetFactory#processParams(ProcessParameterContext)}
     * for each appropriate factory.
     *
     * @see FacetFactory#processParams(ProcessParameterContext)
     *
     * @param introspectedClass
     * @param method
     *            - action method to process
     * @param methodRemover
     * @param facetedMethodParameter
     */
    public void processParams(
            final Class<?> introspectedClass,
            final IntrospectionPolicy introspectionPolicy,
            final MethodFacade method,
            final MethodRemover methodRemover,
            final FacetedMethodParameter facetedMethodParameter) {

        var processParameterContext =
                new ProcessParameterContext(introspectedClass, introspectionPolicy,
                        method, methodRemover, facetedMethodParameter);

        FeatureType.PARAMETERS_ONLY.stream()
            .map(factoriesByFeatureType::getOrElseEmpty)
            .flatMap(List::stream)
            .collect(Collectors.toSet())
            .forEach(facetFactory->facetFactory.processParams(processParameterContext));
    }

    // -- INITIALIZERS

    private static ListMultimap<FeatureType, FacetFactory> factoriesByFeatureType(final Iterable<FacetFactory> factories) {
        var factoryListByFeatureType = _Multimaps.<FeatureType, FacetFactory>newListMultimap();
        for (var factory : factories) {
            factory.getFeatureTypes().forEach(featureType->
                factoryListByFeatureType.putElement(featureType, factory));
        }
        return factoryListByFeatureType;
    }

    private static Set<String> methodPrefixes(final Iterable<FacetFactory> factories) {
        var cachedMethodPrefixes = _Sets.<String>newHashSet();
        for (var facetFactory : factories) {
            if (facetFactory instanceof MethodPrefixBasedFacetFactory methodPrefixBasedFacetFactory) {
                methodPrefixBasedFacetFactory.getPrefixes().forEach(cachedMethodPrefixes::add);
            }
        }
        return cachedMethodPrefixes;
    }

    private static List<MethodFilteringFacetFactory> methodFilteringFactories(final Iterable<FacetFactory> factories) {
        var methodFilteringFactories = new ArrayList<MethodFilteringFacetFactory>();
        for (var factory : factories) {
            if (factory instanceof MethodFilteringFacetFactory methodFilteringFacetFactory) {
                methodFilteringFactories.add(methodFilteringFacetFactory);
            }
        }
        return methodFilteringFactories;
    }

    private static List<AccessorFacetFactory> propertyAccessorFactories(final Iterable<FacetFactory> factories) {
        var propertyOrCollectionIdentifyingFactories = new ArrayList<AccessorFacetFactory>();
        for (var factory : factories) {
            if (factory instanceof AccessorFacetFactory accessorFacetFactory) {
                if(!accessorFacetFactory.supportsProperties()) continue;
                propertyOrCollectionIdentifyingFactories.add(accessorFacetFactory);
            }
        }
        return propertyOrCollectionIdentifyingFactories;
    }
    private static List<AccessorFacetFactory> collectionAccessorFactories(final Iterable<FacetFactory> factories) {
        var propertyOrCollectionIdentifyingFactories = new ArrayList<AccessorFacetFactory>();
        for (var factory : factories) {
            if (factory instanceof AccessorFacetFactory accessorFacetFactory) {
                if(!accessorFacetFactory.supportsCollections()) continue;
                propertyOrCollectionIdentifyingFactories.add(accessorFacetFactory);
            }
        }
        return propertyOrCollectionIdentifyingFactories;
    }

    private static List<ObjectTypeFacetFactory> objectSpecIfFacetFactoryList(
            final ListMultimap<FeatureType, FacetFactory> factoryListByFeatureType,
            final Iterable<FacetFactory> factories) {
            var facetFactories = new ArrayList<ObjectTypeFacetFactory>();
            factoryListByFeatureType.getOrElseEmpty(FeatureType.OBJECT)
            .forEach(facetFactory->{
                if (facetFactory instanceof ObjectTypeFacetFactory objectTypeFacetFactory) {
                    facetFactories.add(objectTypeFacetFactory);
                }
            });
            return Collections.unmodifiableList(facetFactories);
    }

    // -- HELPER

    private static MethodRemover removerElseNoopRemover(final MethodRemover methodRemover) {
        return methodRemover != null ? methodRemover : MethodRemover.NOOP;
    }

}