All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.bval.jsr.metadata.HierarchyBuilder Maven / Gradle / Ivy

/*
 *  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.bval.jsr.metadata;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import jakarta.validation.ElementKind;
import jakarta.validation.ParameterNameProvider;

import org.apache.bval.jsr.ApacheValidatorFactory;
import org.apache.bval.jsr.groups.GroupConversion;
import org.apache.bval.jsr.util.Methods;
import org.apache.bval.jsr.util.Proxies;
import org.apache.bval.util.Exceptions;
import org.apache.bval.util.Validate;
import org.apache.bval.util.reflection.Reflection;
import org.apache.bval.util.reflection.Reflection.Interfaces;
import org.apache.commons.weaver.privilizer.Privilizing;
import org.apache.commons.weaver.privilizer.Privilizing.CallTo;

@Privilizing(@CallTo(Reflection.class))
public class HierarchyBuilder extends CompositeBuilder {
    static abstract class HierarchyDelegate {
        final D delegate;
        final Meta hierarchyElement;

        HierarchyDelegate(D delegate, Meta hierarchyElement) {
            super();
            this.delegate = Validate.notNull(delegate, "delegate");
            this.hierarchyElement = Validate.notNull(hierarchyElement, "hierarchyElement");
        }

        Meta getHierarchyElement() {
            return hierarchyElement;
        }
    }

    static abstract class ElementDelegate>
        extends HierarchyDelegate {

        ElementDelegate(T delegate, Meta hierarchyElement) {
            super(delegate, hierarchyElement);
        }

        Annotation[] getDeclaredConstraints() {
            return delegate.getDeclaredConstraints(hierarchyElement);
        }
    }

    private class BeanDelegate extends HierarchyDelegate, MetadataBuilder.ForBean>
        implements MetadataBuilder.ForBean {

        BeanDelegate(MetadataBuilder.ForBean delegate, Class hierarchyType) {
            super(delegate, new Meta.ForClass(hierarchyType));
        }

        @Override
        public MetadataBuilder.ForClass getClass(Meta> meta) {
            return new ClassDelegate<>(delegate.getClass(hierarchyElement), hierarchyElement);
        }

        @Override
        public Map> getFields(Meta> meta) {
            final Map> fields = delegate.getFields(hierarchyElement);

            if (fields.isEmpty()) {
                return fields;
            }
            final Map> result = new LinkedHashMap<>();

            fields.forEach((k, v) -> {
                final Field fld = Reflection.getDeclaredField(hierarchyElement.getHost(), k);
                Exceptions.raiseIf(fld == null, IllegalStateException::new, "delegate builder specified unknown field");
                result.put(k, new ContainerDelegate(v, new Meta.ForField(fld)));
            });
            return result;
        }

        @Override
        public Map>> getConstructors(Meta> meta) {
            if (hierarchyElement.equals(meta)) {
                @SuppressWarnings("unchecked")
                final Map>> result =
                    ((MetadataBuilder.ForBean) delegate).getConstructors(meta);
                return result;
            }
            // ignore hierarchical ctors:
            return Collections.emptyMap();
        }

        @Override
        public Map> getGetters(Meta> meta) {
            final Map> getters = delegate.getGetters(hierarchyElement);
            if (getters.isEmpty()) {
                return getters;
            }
            final Map> result = new LinkedHashMap<>();
            final List> delegates = new ArrayList<>();

            getters.forEach((k, v) -> {
                final Method getter = Methods.getter(hierarchyElement.getHost(), k);

                Exceptions.raiseIf(getter == null, IllegalStateException::new,
                    "delegate builder specified unknown getter");

                final ContainerDelegate d = new ContainerDelegate<>(v, new Meta.ForMethod(getter));
                result.put(k, d);
                delegates.add(d);
            });
            Liskov.validateValidateOnExecution(delegates);

            return result;
        }

        @Override
        public Map> getMethods(Meta> meta) {
            final Map> methods = delegate.getMethods(hierarchyElement);
            if (methods.isEmpty()) {
                return methods;
            }
            final Map> result = new LinkedHashMap<>();
            final List> delegates = new ArrayList<>();
            methods.forEach((k, v) -> {
                final ExecutableDelegate d = new ExecutableDelegate<>(v,
                    new Meta.ForMethod(
                        Reflection.getDeclaredMethod(hierarchyElement.getHost(), k.getName(), k.getParameterTypes())),
                    ParameterNameProvider::getParameterNames);
                result.put(k, d);
                delegates.add(d);
            });
            Liskov.validateValidateOnExecution(delegates);

            return result;
        }
    }

    private class ClassDelegate extends ElementDelegate, MetadataBuilder.ForClass>
        implements MetadataBuilder.ForClass {

        ClassDelegate(MetadataBuilder.ForClass delegate, Meta> hierarchyType) {
            super(delegate, hierarchyType);
        }

        @Override
        public List> getGroupSequence(Meta> meta) {
            return delegate.getGroupSequence(hierarchyElement);
        }

        @Override
        public Annotation[] getDeclaredConstraints(Meta> meta) {
            return getDeclaredConstraints();
        }
    }

    class ContainerDelegate extends ElementDelegate>
        implements MetadataBuilder.ForContainer {

        ContainerDelegate(MetadataBuilder.ForContainer delegate, Meta hierarchyElement) {
            super(delegate, hierarchyElement);
        }
        
        boolean isCascade() {
            return delegate.isCascade(hierarchyElement);
        }

        @Override
        public final boolean isCascade(Meta meta) {
            return isCascade();
        }

        Set getGroupConversions() {
            return delegate.getGroupConversions(hierarchyElement);
        }

        @Override
        public final Set getGroupConversions(Meta meta) {
            return getGroupConversions();
        }

        @Override
        public Map> getContainerElementTypes(
            Meta meta) {
            final Map> containerElementTypes =
                delegate.getContainerElementTypes(hierarchyElement);

            final Map> result = new LinkedHashMap<>();

            containerElementTypes.forEach((k, v) -> {
                result.put(k, new ContainerDelegate<>(v, new Meta.ForContainerElement(hierarchyElement, k)));
            });
            return result;
        }

        @Override
        public Annotation[] getDeclaredConstraints(Meta meta) {
            return getDeclaredConstraints();
        }
    }

    private class ExecutableDelegate
        extends HierarchyDelegate> implements MetadataBuilder.ForExecutable {

        final BiFunction> getParameterNames;

        ExecutableDelegate(MetadataBuilder.ForExecutable delegate, Meta hierarchyElement,
            BiFunction> getParameterNames) {
            super(delegate, hierarchyElement);
            this.getParameterNames = Validate.notNull(getParameterNames, "getParameterNames");
        }

        @Override
        public MetadataBuilder.ForContainer getReturnValue(Meta meta) {
            return new ContainerDelegate<>(delegate.getReturnValue(hierarchyElement), hierarchyElement);
        }

        @Override
        public MetadataBuilder.ForElement getCrossParameter(Meta meta) {
            return new CrossParameterDelegate<>(delegate.getCrossParameter(hierarchyElement), hierarchyElement);
        }

        @Override
        public List> getParameters(Meta meta) {
            final List> parameterDelegates =
                delegate.getParameters(hierarchyElement);

            if (parameterDelegates.isEmpty()) {
                return parameterDelegates;
            }
            final List> metaParameters = getMetaParameters(hierarchyElement, getParameterNames);

            if (metaParameters.size() != parameterDelegates.size()) {
                Exceptions.raise(IllegalStateException::new, "Got wrong number of parameter delegates for %s",
                    meta.getHost());
            }
            return IntStream.range(0, parameterDelegates.size())
                .mapToObj(n -> new ContainerDelegate<>(parameterDelegates.get(n), metaParameters.get(n)))
                .collect(Collectors.toList());
        }
    }

    private class CrossParameterDelegate
        extends ElementDelegate> implements MetadataBuilder.ForElement {

        CrossParameterDelegate(MetadataBuilder.ForElement delegate, Meta hierarchyElement) {
            super(delegate, hierarchyElement);
        }

        @Override
        public Annotation[] getDeclaredConstraints(Meta meta) {
            return getDeclaredConstraints();
        }
    }

    private class ForCrossParameter
        extends CompositeBuilder.ForElement, E> {

        ForCrossParameter(List> delegates) {
            super(delegates);
            Liskov.validateCrossParameterHierarchy(delegates);
        }
    }

    private class ForContainer
        extends CompositeBuilder.ForContainer, E> {

        ForContainer(List> delegates, ElementKind elementKind) {
            super(delegates);
            Liskov.validateContainerHierarchy(delegates, Validate.notNull(elementKind, "elementKind"));
        }
    }

    private final Function, MetadataBuilder.ForBean> getBeanBuilder;

    public HierarchyBuilder(ApacheValidatorFactory validatorFactory,
        Function, MetadataBuilder.ForBean> getBeanBuilder) {
        super(validatorFactory, AnnotationBehaviorMergeStrategy.first());
        this.getBeanBuilder = Validate.notNull(getBeanBuilder, "getBeanBuilder function was null");
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    public  MetadataBuilder.ForBean forBean(Class beanClass) {
        final List> delegates = new ArrayList<>();

        /*
         * First add the delegate for the requested bean class, forcing to empty if absent. This is important for the
         * same reason that we use the #first() AnnotationBehaviorMergeStrategy: namely, that custom metadata overrides
         * only from the immediately available mapping per the BV spec.
         */
        delegates.add(Optional.of(beanClass).map(getBeanBuilder).orElseGet(() -> EmptyBuilder.instance().forBean()));

        // iterate the hierarchy, skipping the first (i.e. beanClass handled above)
        final Iterator> hierarchy = Reflection.hierarchy(beanClass, Interfaces.INCLUDE).iterator();
        hierarchy.next();

        // filter; map; skip empty hierarchy builders, mapping others to BeanDelegate
        hierarchy.forEachRemaining(t -> Optional.of(t).filter(this::canValidate).map(getBeanBuilder)
            .filter(b -> !b.isEmpty()).map(b -> new BeanDelegate(b, t)).ifPresent(delegates::add));

        if (delegates.size() == 1) {
            return (MetadataBuilder.ForBean) delegates.get(0);
        }
        // pretend:
        // note: stream split for java 11 compilation
        final Stream> forBeanStream = delegates.stream()
                .map(MetadataBuilder.ForBean.class::cast);
        return forBeanStream.collect(compose());
    }

    @Override
    protected  Map, Annotation[]> getConstraintDeclarationMap(
        CompositeBuilder.ForElement, E> composite, Meta meta) {

        @SuppressWarnings("unchecked")
        final Function, Meta> keyMapper =
            d -> Optional.of(d).filter(HierarchyDelegate.class::isInstance).map(HierarchyDelegate.class::cast)
                .map(HierarchyDelegate::getHierarchyElement).map(Meta.class::cast).orElse(meta);

        return composite.delegates.stream().collect(Collectors.toMap(keyMapper, d -> d.getDeclaredConstraints(meta),
            (u, v) -> Stream.of(u, v).flatMap(Stream::of).toArray(Annotation[]::new), LinkedHashMap::new));
    }

    @Override
    protected  List> getGroupSequence(CompositeBuilder.ForClass composite, Meta> meta) {
        return composite.delegates.get(0).getGroupSequence(meta);
    }

    @Override
    protected , E extends AnnotatedElement> MetadataBuilder.ForContainer forContainer(
        List delegates, Meta meta, ElementKind elementKind) {

        if (delegates.isEmpty()) {
            return super.forContainer(delegates, meta, elementKind);
        }
        final List> hierarchyDelegates = delegates.stream()
            .> map(
                d -> d instanceof ContainerDelegate ? (ContainerDelegate) d : new ContainerDelegate<>(d, meta))
            .collect(Collectors.toList());

        @SuppressWarnings("unchecked")
        final CompositeBuilder.ForContainer result =
            (CompositeBuilder.ForContainer) new HierarchyBuilder.ForContainer(hierarchyDelegates,
                elementKind);

        return result;
    }

    @Override
    protected , E extends Executable> MetadataBuilder.ForElement forCrossParameter(
        List delegates, Meta meta) {

        if (delegates.isEmpty()) {
            return super.forCrossParameter(delegates, meta);
        }
        final List> hierarchyDelegates =
            delegates.stream()
                .> map(d -> d instanceof CrossParameterDelegate
                    ? (CrossParameterDelegate) d : new CrossParameterDelegate<>(d, meta))
                .collect(Collectors.toList());
        return new HierarchyBuilder.ForCrossParameter<>(hierarchyDelegates);
    }

    private boolean canValidate(Class t) {
        return !(t.getName().startsWith("java.") || Proxies.isProxyClass(t));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy