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

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

There is a newer version: 10.0.0-M3
Show newest version
/*
 *  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.AnnotatedParameterizedType;
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.Modifier;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import javax.validation.ConstraintDeclarationException;
import javax.validation.ConstraintTarget;
import javax.validation.GroupSequence;
import javax.validation.ParameterNameProvider;
import javax.validation.Valid;
import javax.validation.constraintvalidation.ValidationTarget;
import javax.validation.groups.ConvertGroup;

import org.apache.bval.jsr.ApacheValidatorFactory;
import org.apache.bval.jsr.ConstraintAnnotationAttributes;
import org.apache.bval.jsr.groups.GroupConversion;
import org.apache.bval.jsr.util.AnnotationsManager;
import org.apache.bval.jsr.util.Methods;
import org.apache.bval.jsr.util.ToUnmodifiable;
import org.apache.bval.util.Exceptions;
import org.apache.bval.util.Lazy;
import org.apache.bval.util.ObjectUtils;
import org.apache.bval.util.Validate;
import org.apache.bval.util.reflection.Reflection;
import org.apache.commons.weaver.privilizer.Privilizing;
import org.apache.commons.weaver.privilizer.Privilizing.CallTo;

@Privilizing(@CallTo(Reflection.class))
public class ReflectionBuilder {

    private class ForBean implements MetadataBuilder.ForBean {
        private final Meta> meta;

        ForBean(Meta> meta) {
            super();
            this.meta = Validate.notNull(meta, "meta");
        }

        @Override
        public MetadataBuilder.ForClass getClass(Meta> ignored) {
            return new ReflectionBuilder.ForClass<>(meta);
        }

        @Override
        public Map> getFields(Meta> ignored) {
            final Field[] declaredFields = Reflection.getDeclaredFields(meta.getHost());
            if (declaredFields.length == 0) {
                return Collections.emptyMap();
            }
            return Stream.of(declaredFields).filter(f -> !(Modifier.isStatic(f.getModifiers()) || f.isSynthetic()))
                .collect(
                    Collectors.toMap(Field::getName, f -> new ReflectionBuilder.ForContainer<>(new Meta.ForField(f))));
        }

        @Override
        public Map> getGetters(Meta> ignored) {
            final Method[] declaredMethods = Reflection.getDeclaredMethods(meta.getHost());
            if (declaredMethods.length == 0) {
                return Collections.emptyMap();
            }
            final Map> getters = new HashMap<>();
            for (Method m : declaredMethods) {
                if (Methods.isGetter(m)) {
                    getters.computeIfAbsent(Methods.propertyName(m), k -> new LinkedHashSet<>()).add(m);
                }
            }
            final Map> result = new TreeMap<>();
            getters.forEach((k, methods) -> {
                if ("class".equals(k)) {
                    return;
                }
                final List> delegates = methods.stream()
                    .map(g -> new ReflectionBuilder.ForContainer<>(new Meta.ForMethod(g))).collect(Collectors.toList());
                if (delegates.isEmpty()) {
                    return;
                }
                final MetadataBuilder.ForContainer builder;
                if (delegates.size() == 1) {
                    builder = delegates.get(0);
                } else {
                    builder = compositeBuilder.get().new ForContainer<>(delegates);
                }
                result.put(k, builder);
            });
            return result;
        }

        @Override
        public Map>> getConstructors(Meta> ignored) {
            final Constructor[] declaredConstructors = Reflection.getDeclaredConstructors(meta.getHost());
            if (declaredConstructors.length == 0) {
                return Collections.emptyMap();
            }
            return Stream.of(declaredConstructors).collect(
                Collectors.toMap(Signature::of, c -> new ReflectionBuilder.ForExecutable<>(new Meta.ForConstructor<>(c),
                    ParameterNameProvider::getParameterNames)));
        }

        @Override
        public Map> getMethods(Meta> ignored) {
            final Method[] declaredMethods = Reflection.getDeclaredMethods(meta.getHost());
            if (declaredMethods.length == 0) {
                return Collections.emptyMap();
            }
            final Map> methodsBySignature = new HashMap<>();
            for (Method m : declaredMethods) {
                if (!Modifier.isStatic(m.getModifiers())) {
                    methodsBySignature.computeIfAbsent(Signature.of(m), k -> new LinkedHashSet<>()).add(m);
                }
            }
            final Map> result = new TreeMap<>();

            // we can't filter the getters since they can be validated, todo: read the config to know if we need or not

            methodsBySignature.forEach((sig, methods) -> {
                final List> delegates =
                    methods.stream().map(g -> new ReflectionBuilder.ForExecutable<>(new Meta.ForMethod(g),
                        ParameterNameProvider::getParameterNames)).collect(Collectors.toList());
                if (delegates.isEmpty()) {
                    return;
                }
                final MetadataBuilder.ForExecutable builder;
                if (delegates.size() == 1) {
                    builder = delegates.get(0);
                } else {
                    builder = compositeBuilder.get().new ForExecutable, Method>(
                        delegates, ParameterNameProvider::getParameterNames);
                }
                result.put(sig, builder);
            });
            return result;
        }
    }

    private abstract class ForElement implements MetadataBuilder.ForElement {
        final Meta meta;

        ForElement(Meta meta) {
            super();
            this.meta = Validate.notNull(meta, "meta");
        }

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

        @Override
        public boolean equals(Object obj) {
            return obj == this || this.getClass().isInstance(obj) && ((ForElement) obj).meta.equals(meta);
        }

        @Override
        public int hashCode() {
            return Objects.hash(getClass(), meta);
        }
    }

    private class ForClass extends ForElement> implements MetadataBuilder.ForClass {

        ForClass(Meta> meta) {
            super(meta);
        }

        @Override
        public List> getGroupSequence(Meta> ignored) {
            final GroupSequence groupSequence = AnnotationsManager.getAnnotation(meta.getHost(), GroupSequence.class);
            return groupSequence == null ? null : Collections.unmodifiableList(Arrays.asList(groupSequence.value()));
        }
    }

    private class ForContainer extends ReflectionBuilder.ForElement
        implements MetadataBuilder.ForContainer {

        ForContainer(Meta meta) {
            super(meta);
        }

        @Override
        public Map> getContainerElementTypes(
            Meta ignored) {
            final AnnotatedType annotatedType = meta.getAnnotatedType();
            if (annotatedType instanceof AnnotatedParameterizedType) {

                final AnnotatedParameterizedType container = (AnnotatedParameterizedType) annotatedType;

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

                final AnnotatedType[] typeArgs = container.getAnnotatedActualTypeArguments();
                for (int i = 0; i < typeArgs.length; i++) {
                    final ContainerElementKey key = new ContainerElementKey(container, i);
                    result.put(key, new ReflectionBuilder.ForContainer<>(new Meta.ForContainerElement(meta, key)));
                }
                return result;
            }
            return Collections.emptyMap();
        }

        @Override
        public boolean isCascade(Meta ignored) {
            return AnnotationsManager.isAnnotationDirectlyPresent(meta.getHost(), Valid.class);
        }

        @Override
        public Set getGroupConversions(Meta ignored) {
            return Stream.of(AnnotationsManager.getDeclaredAnnotationsByType(meta.getHost(), ConvertGroup.class))
                .map(cg -> GroupConversion.from(cg.from()).to(cg.to())).collect(ToUnmodifiable.set());
        }
    }

    private class ForExecutable implements MetadataBuilder.ForExecutable {

        final Meta meta;
        final BiFunction> getParameterNames;

        ForExecutable(Meta meta, BiFunction> getParameterNames) {
            super();
            this.meta = Validate.notNull(meta, "meta");
            this.getParameterNames = Validate.notNull(getParameterNames, "getParameterNames");
        }

        @Override
        public List> getParameters(Meta ignored) {
            final Parameter[] parameters = meta.getHost().getParameters();
            if (parameters.length == 0) {
                return Collections.emptyList();
            }
            final List parameterNames = getParameterNames.apply(validatorFactory.getParameterNameProvider(),meta.getHost());

            return IntStream.range(0, parameters.length).mapToObj(
                n -> new ReflectionBuilder.ForContainer<>(new Meta.ForParameter(parameters[n], parameterNames.get(n))))
                .collect(ToUnmodifiable.list());
        }

        @Override
        public ForContainer getReturnValue(Meta ignored) {
            return new ReflectionBuilder.ForContainer(meta) {

                @Override
                public Annotation[] getDeclaredConstraints(Meta meta) {
                    return getConstraints(ConstraintTarget.RETURN_VALUE);
                }
            };
        }

        @Override
        public MetadataBuilder.ForElement getCrossParameter(Meta ignored) {
            return new ReflectionBuilder.ForElement(meta) {
                @Override
                public Annotation[] getDeclaredConstraints(Meta meta) {
                    return getConstraints(ConstraintTarget.PARAMETERS);
                }
            };
        }

        private Annotation[] getConstraints(ConstraintTarget constraintTarget) {
            return Optional.of(getConstraintsByTarget()).map(m -> m.get(constraintTarget))
                .map(l -> l.toArray(new Annotation[l.size()])).orElse(ObjectUtils.EMPTY_ANNOTATION_ARRAY);
        }

        private Map> getConstraintsByTarget() {
            final Annotation[] declaredConstraints = AnnotationsManager.getDeclaredConstraints(meta);
            if (ObjectUtils.isEmptyArray(declaredConstraints)) {
                return Collections.emptyMap();
            }
            final Map> result = new EnumMap<>(ConstraintTarget.class);

            for (Annotation constraint : declaredConstraints) {
                final Class constraintType = constraint.annotationType();
                final Optional explicitTarget =
                    Optional.of(ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO.analyze(constraintType))
                        .filter(ConstraintAnnotationAttributes.Worker::isValid)
                        . map(w -> w.read(constraint)).filter(et -> et != ConstraintTarget.IMPLICIT);

                final ConstraintTarget target;

                if (explicitTarget.isPresent()) {
                    target = explicitTarget.get();
                } else {
                    final Set supportedTargets =
                        validatorFactory.getAnnotationsManager().supportedTargets(constraintType);

                    if (supportedTargets.size() == 1) {
                        final ValidationTarget validationTarget = supportedTargets.iterator().next();
                        switch (validationTarget) {
                        case PARAMETERS:
                            target = ConstraintTarget.PARAMETERS;
                            break;
                        case ANNOTATED_ELEMENT:
                            target = ConstraintTarget.RETURN_VALUE;
                            break;
                        default:
                            throw Exceptions.create(IllegalStateException::new, "Unknown %s %s for %s",
                                ValidationTarget.class.getSimpleName(), validationTarget, constraintType);
                        }
                    } else {
                        target = impliedConstraintTarget();
                        if (target == null) {
                            Exceptions.raise(ConstraintDeclarationException::new,
                                "Found %d possible %s types for constraint type %s and no explicit assignment via #%s()",
                                supportedTargets.size(), ValidationTarget.class.getSimpleName(),
                                constraintType.getName(),
                                ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO.getAttributeName());
                        }
                    }
                }
                result.computeIfAbsent(target, k -> new ArrayList<>()).add(constraint);
            }
            return result;
        }

        private ConstraintTarget impliedConstraintTarget() {
            if (meta.getHost().getParameterCount() == 0) {
                return ConstraintTarget.RETURN_VALUE;
            }
            if (Void.TYPE.equals(meta.getType())) {
                return ConstraintTarget.PARAMETERS;
            }
            return null;
        }
    }

    private final ApacheValidatorFactory validatorFactory;
    private final Lazy compositeBuilder;

    public ReflectionBuilder(ApacheValidatorFactory validatorFactory) {
        super();
        this.validatorFactory = Validate.notNull(validatorFactory, "validatorFactory");
        this.compositeBuilder =
            new Lazy<>(() -> new CompositeBuilder(this.validatorFactory, x -> AnnotationBehavior.ABSTAIN));
    }

    public  MetadataBuilder.ForBean forBean(Class beanClass) {
        return new ReflectionBuilder.ForBean<>(new Meta.ForClass(beanClass));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy