public record MethodFinder()

in core/metamodel/src/main/java/org/apache/causeway/core/metamodel/methods/MethodFinder.java [44:268]


public record MethodFinder(
        @NonNull Class<?> correspondingClass,
        @NonNull Can<String> methodNameCandidates,
        @NonNull EncapsulationPolicy encapsulationPolicy,
        @NonNull Predicate<ResolvedMethod> mustSatisfy) {

    public static final Can<String> ANY_NAME = Can.of(""); // arbitrary marker
    public static final Class<?>[] NO_ARG = new Class<?>[0];

    public static MethodFinder of(
            final @NonNull Class<?> correspondingClass,
            final @NonNull Can<String> methodNameCandidatesPossiblyDuplicated,
            final @NonNull EncapsulationPolicy encapsulationPolicy,
            final @NonNull Predicate<ResolvedMethod> mustSatisfy) {

        final Predicate<ResolvedMethod> isNotStatic = MethodUtil::isNotStatic;
        var methodNameCandidates = methodNameCandidatesPossiblyDuplicated.distinct();

        return new MethodFinder(
                correspondingClass,
                methodNameCandidates,
                encapsulationPolicy,
                methodNameCandidates.equals(ANY_NAME)
                        ? isNotStatic.and(mustSatisfy)
                        : isNotStatic
                            .and(method->methodNameCandidates.contains(method.name()))
                            .and(mustSatisfy));
    }

    public static MethodFinder notNecessarilyPublic(
            final Class<?> correspondingClass,
            final Can<String> methodNameCandidates) {
        return of(
                correspondingClass,
                methodNameCandidates,
                EncapsulationPolicy.ENCAPSULATED_MEMBERS_SUPPORTED,
                _Predicates.alwaysTrue());
    }

    public static MethodFinder publicOnly(
            final Class<?> correspondingClass,
            final Can<String> methodNameCandidates) {
        return of(
                correspondingClass,
                methodNameCandidates,
                EncapsulationPolicy.ONLY_PUBLIC_MEMBERS_SUPPORTED,
                _Predicates.alwaysTrue());
    }

    public static MethodFinder accessor(
            final Class<?> correspondingClass,
            final Can<String> methodNameCandidates,
            final IntrospectionPolicy memberIntrospectionPolicy) {
        return havingAnyOrNoAnnotation(
                correspondingClass,
                methodNameCandidates,
                memberIntrospectionPolicy);
    }

    public static MethodFinder objectSupport(
            final Class<?> correspondingClass,
            final Can<String> methodNameCandidates,
            final IntrospectionPolicy memberIntrospectionPolicy) {
        return supportMethod(
                correspondingClass,
                methodNameCandidates,
                memberIntrospectionPolicy,
                Domain.Include.class,
                ProgrammingModelConstants.ConflictingAnnotations.OBJECT_SUPPORT);
    }

    public static MethodFinder livecycleCallback(
            final Class<?> correspondingClass,
            final Can<String> methodNameCandidates,
            final IntrospectionPolicy memberIntrospectionPolicy) {
        return supportMethod(
                correspondingClass,
                methodNameCandidates,
                memberIntrospectionPolicy,
                Domain.Include.class,
                ProgrammingModelConstants.ConflictingAnnotations.OBJECT_LIFECYCLE);
    }

    public static MethodFinder memberSupport(
            final Class<?> correspondingClass,
            final Can<String> methodNameCandidates,
            final IntrospectionPolicy memberIntrospectionPolicy) {
        return supportMethod(
                correspondingClass,
                methodNameCandidates,
                memberIntrospectionPolicy,
                Domain.Include.class,
                ProgrammingModelConstants.ConflictingAnnotations.MEMBER_SUPPORT);
    }

    public Stream<ResolvedMethod> streamMethodsMatchingSignature(
            final @Nullable Class<?>[] paramTypes) {

        if(paramTypes==null) return streamMethodsIgnoringSignature();

        var type = correspondingClass();
        var classCache = _ClassCache.getInstance();
        var isEncapsulationSupported = encapsulationPolicy().isEncapsulatedMembersSupported();

        if(methodNameCandidates.equals(ANY_NAME)) {
            //stream all
            return (isEncapsulationSupported
                    ? classCache.streamResolvedMethods(type)
                    : classCache.streamPublicMethods(type))
                        .filter(method->Arrays.equals(paramTypes, method.paramTypes()))
                        .filter(mustSatisfy);
        }

        return methodNameCandidates.stream()
            .map(name->isEncapsulationSupported
                    ? classCache.lookupResolvedMethod(type, name, paramTypes)
                    : classCache.lookupPublicMethod(type, name, paramTypes))
            .filter(Optional::isPresent)
            .map(Optional::get)
            .filter(mustSatisfy);
    }

    public Stream<ResolvedMethod> streamMethodsIgnoringSignature() {
        var type = correspondingClass();
        var classCache = _ClassCache.getInstance();
        var isEncapsulationSupported = encapsulationPolicy().isEncapsulatedMembersSupported();
        return (isEncapsulationSupported
                ? classCache.streamResolvedMethods(type)
                : classCache.streamPublicMethods(type))
                    .filter(mustSatisfy);
    }

    // -- WITHERS

    public MethodFinder withRequiredReturnType(final @NonNull Class<?> requiredReturnType) {
        return new MethodFinder(
                correspondingClass,
                methodNameCandidates,
                encapsulationPolicy,
                mustSatisfy.and(resolvedMethod->resolvedMethod.isReturnTypeATypeOf(requiredReturnType)));
    }

    public MethodFinder withReturnTypeAnyOf(final @NonNull Can<Class<?>> anyOfReturnTypes) {
        return new MethodFinder(
                correspondingClass,
                methodNameCandidates,
                encapsulationPolicy,
                mustSatisfy.and(resolvedMethod->resolvedMethod.isReturnTypeAnyTypeOf(anyOfReturnTypes)));
    }

    // -- HELPER

    private static MethodFinder havingAnyOrNoAnnotation(
            final Class<?> correspondingClass,
            final Can<String> methodNameCandidates,
            final IntrospectionPolicy memberIntrospectionPolicy) {
        return of(
                correspondingClass,
                methodNameCandidates,
                memberIntrospectionPolicy.getEncapsulationPolicy(),
                _Predicates.alwaysTrue());
    }

    private static MethodFinder supportMethod(
            final Class<?> correspondingClass,
            final Can<String> methodNameCandidates,
            final IntrospectionPolicy memberIntrospectionPolicy,
            final Class<? extends Annotation> annotationType,
            final ConflictingAnnotations conflictingAnnotations) {

        var finder = of(
                correspondingClass,
                methodNameCandidates,
                // support methods are always allowed private
                EncapsulationPolicy.ENCAPSULATED_MEMBERS_SUPPORTED,
                havingAnnotationIfEnforcedByPolicyOrAccessibility(
                        memberIntrospectionPolicy.getSupportMethodAnnotationPolicy().isSupportMethodAnnotationsRequired(),
                        annotationType,
                        conflictingAnnotations.getProhibits()));

        return finder;
    }

    private static Predicate<ResolvedMethod> havingAnnotationIfEnforcedByPolicyOrAccessibility(
            final boolean annotationRequired,
            final Class<? extends Annotation> annotationType,
            final Can<Class<? extends Annotation>> conflictingAnnotations) {

        return annotationRequired
                    ? method->havingAnnotation(method, annotationType, conflictingAnnotations)
                    : method->havingAnnotationOrPublic(method, annotationType, conflictingAnnotations);
    }

    private static boolean havingAnnotationOrPublic(
            final ResolvedMethod method,
            final Class<? extends Annotation> annotationType,
            final Can<Class<? extends Annotation>> conflictingAnnotations) {

        return _Reflect.isPublicNonSynthetic(method.method())
                ? true
                : havingAnnotation(method, annotationType, conflictingAnnotations);
    }

    //FIXME[CAUSEWAY-2774] if annotation appears on an abstract method that was inherited with given method,
    // its not detected here
    private static boolean havingAnnotation(
            final ResolvedMethod method,
            final Class<? extends Annotation> annotationType,
            final Can<Class<? extends Annotation>> conflictingAnnotations) {

        var isMarkerAnnotationPresent = _Annotations.synthesize(method.method(), annotationType).isPresent();
        if(isMarkerAnnotationPresent) {

            var isConflictingAnnotationPresent = conflictingAnnotations
            .stream()
            .anyMatch(conflictingAnnotationType->
                    _Annotations.synthesize(method.method(), conflictingAnnotationType).isPresent());

            // do not pickup this method if conflicting - so meta-model validation will fail later on
            return !isConflictingAnnotationPresent;
        }
        return isMarkerAnnotationPresent;
    }

}