BV2: new descriptor model
Project: http://git-wip-us.apache.org/repos/asf/bval/repo Commit: http://git-wip-us.apache.org/repos/asf/bval/commit/40ac09f7 Tree: http://git-wip-us.apache.org/repos/asf/bval/tree/40ac09f7 Diff: http://git-wip-us.apache.org/repos/asf/bval/diff/40ac09f7 Branch: refs/heads/bv2 Commit: 40ac09f77a673fc61863304a72facc38f2ef2e42 Parents: 59bd964 Author: Matt Benson <[email protected]> Authored: Wed Feb 21 14:50:59 2018 -0600 Committer: Matt Benson <[email protected]> Committed: Wed Feb 21 14:59:57 2018 -0600 ---------------------------------------------------------------------- .../org/apache/bval/jsr/descriptor/BeanD.java | 128 ++++++++ .../jsr/descriptor/CascadableContainerD.java | 88 ++++++ .../apache/bval/jsr/descriptor/ComposedD.java | 123 ++++++++ .../ComputeConstraintValidatorClass.java | 183 ++++++++++++ .../apache/bval/jsr/descriptor/ConstraintD.java | 276 ++++++++++++++++++ .../bval/jsr/descriptor/ConstructorD.java | 41 +++ .../jsr/descriptor/ContainerElementTypeD.java | 119 ++++++++ .../bval/jsr/descriptor/CrossParameterD.java | 18 ++ .../bval/jsr/descriptor/DescriptorManager.java | 74 +++++ .../apache/bval/jsr/descriptor/ElementD.java | 122 ++++++++ .../apache/bval/jsr/descriptor/ExecutableD.java | 84 ++++++ .../org/apache/bval/jsr/descriptor/Finder.java | 103 +++++++ .../bval/jsr/descriptor/GroupConversion.java | 85 ++++++ .../bval/jsr/descriptor/MetadataReader.java | 291 +++++++++++++++++++ .../org/apache/bval/jsr/descriptor/MethodD.java | 49 ++++ .../apache/bval/jsr/descriptor/ParameterD.java | 62 ++++ .../apache/bval/jsr/descriptor/PropertyD.java | 106 +++++++ .../bval/jsr/descriptor/ReturnValueD.java | 36 +++ 18 files changed, 1988 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/BeanD.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/BeanD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/BeanD.java new file mode 100644 index 0000000..7f52c6d --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/BeanD.java @@ -0,0 +1,128 @@ +/* + * 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.descriptor; + +import java.lang.reflect.Type; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import javax.validation.metadata.BeanDescriptor; +import javax.validation.metadata.ConstructorDescriptor; +import javax.validation.metadata.MethodDescriptor; +import javax.validation.metadata.MethodType; +import javax.validation.metadata.PropertyDescriptor; + +import org.apache.bval.jsr.metadata.Signature; +import org.apache.bval.jsr.util.ToUnmodifiable; +import org.apache.bval.util.Exceptions; +import org.apache.bval.util.Lazy; +import org.apache.bval.util.StringUtils; + +public class BeanD extends ElementD<Class<?>, MetadataReader.ForBean> implements BeanDescriptor { + + private static boolean constrainedProperty(PropertyDescriptor pd) { + return pd.hasConstraints() || pd.isCascaded(); + } + + private final Class<?> beanClass; + + private final Lazy<List<Class<?>>> groupSequence; + private final Lazy<Map<String, PropertyDescriptor>> propertiesMap; + private final Lazy<Set<PropertyDescriptor>> properties; + private final Lazy<Map<Signature, ConstructorD>> constructors; + private final Lazy<Map<Signature, MethodD>> methods; + + BeanD(MetadataReader.ForBean reader) { + super(reader); + this.beanClass = reader.meta.getHost(); + + groupSequence = new Lazy<>(reader::getGroupSequence); + propertiesMap = new Lazy<>(() -> reader.getProperties(this)); + properties = new Lazy<>(() -> propertiesMap.get().values().stream().filter(BeanD::constrainedProperty) + .collect(ToUnmodifiable.set())); + constructors = new Lazy<>(() -> reader.getConstructors(this)); + methods = new Lazy<>(() -> reader.getMethods(this)); + } + + @Override + public Class<?> getElementClass() { + return beanClass; + } + + @Override + public boolean isBeanConstrained() { + return hasConstraints() || properties.get().stream().anyMatch(DescriptorManager::isConstrained); + } + + @Override + public PropertyDescriptor getConstraintsForProperty(String propertyName) { + return Optional.ofNullable(getProperty(propertyName)).filter(BeanD::constrainedProperty).orElse(null); + } + + @Override + public Set<PropertyDescriptor> getConstrainedProperties() { + return properties.get(); + } + + @Override + public MethodDescriptor getConstraintsForMethod(String methodName, Class<?>... parameterTypes) { + return methods.get().get(new Signature(methodName, parameterTypes)); + } + + @SuppressWarnings("unlikely-arg-type") + @Override + public Set<MethodDescriptor> getConstrainedMethods(MethodType methodType, MethodType... methodTypes) { + return methods.get().values().stream().filter(EnumSet.of(methodType, methodTypes)::contains) + .collect(ToUnmodifiable.set()); + } + + @Override + public ConstructorDescriptor getConstraintsForConstructor(Class<?>... parameterTypes) { + return constructors.get().get(new Signature(beanClass.getName(), parameterTypes)); + } + + @Override + public Set<ConstructorDescriptor> getConstrainedConstructors() { + return constructors.get().values().stream().collect(ToUnmodifiable.set()); + } + + public PropertyDescriptor getProperty(String propertyName) { + Exceptions.raiseIf(StringUtils.isBlank(propertyName), IllegalArgumentException::new, + "propertyName was null/empty/blank"); + + return propertiesMap.get().get(propertyName); + } + + @Override + protected BeanD getBean() { + return this; + } + + @Override + public List<Class<?>> getGroupSequence() { + return groupSequence.get(); + } + + public final Type getGenericType() { + return getElementClass(); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/CascadableContainerD.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/CascadableContainerD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/CascadableContainerD.java new file mode 100644 index 0000000..8c76fb0 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/CascadableContainerD.java @@ -0,0 +1,88 @@ +/* + * 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.descriptor; + +import java.lang.reflect.AnnotatedElement; +import java.util.Set; +import java.util.stream.Stream; + +import javax.validation.ValidationException; +import javax.validation.metadata.CascadableDescriptor; +import javax.validation.metadata.ContainerDescriptor; +import javax.validation.metadata.ContainerElementTypeDescriptor; +import javax.validation.metadata.GroupConversionDescriptor; + +import org.apache.bval.jsr.GraphContext; +import org.apache.bval.jsr.util.ToUnmodifiable; +import org.apache.bval.util.Lazy; +import org.apache.bval.util.Validate; +import org.apache.bval.util.reflection.TypeUtils; + +public abstract class CascadableContainerD<P extends ElementD<?, ?>, E extends AnnotatedElement> extends + ElementD.NonRoot<P, E, MetadataReader.ForContainer<E>> implements CascadableDescriptor, ContainerDescriptor { + + private final boolean cascaded; + private final Set<GroupConversion> groupConversions; + private final Lazy<Set<ContainerElementTypeD>> containerElementTypes; + + protected CascadableContainerD(MetadataReader.ForContainer<E> reader, P parent) { + super(reader, parent); + cascaded = reader.isCascaded(); + groupConversions = reader.getGroupConversions(); + containerElementTypes = new Lazy<>(() -> reader.getContainerElementTypes(this)); + } + + @Override + public Class<?> getElementClass() { + return TypeUtils.getRawType(getGenericType(), parent.getElementClass()); + } + + @Override + public boolean isCascaded() { + return cascaded; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public Set<GroupConversionDescriptor> getGroupConversions() { + return (Set) groupConversions; + } + + @Override + public Set<ContainerElementTypeDescriptor> getConstrainedContainerElementTypes() { + return containerElementTypes.get().stream().filter(DescriptorManager::isConstrained) + .collect(ToUnmodifiable.set()); + } + + public final Stream<GraphContext> read(GraphContext context) { + Validate.notNull(context); + if (context.getValue() == null) { + return Stream.empty(); + } + try { + return readImpl(context); + } catch (Exception e) { + throw new ValidationException(e); + } + } + + protected Stream<GraphContext> readImpl(GraphContext context) throws Exception { + throw new UnsupportedOperationException(); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ComposedD.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ComposedD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ComposedD.java new file mode 100644 index 0000000..6d3b004 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ComposedD.java @@ -0,0 +1,123 @@ +/* + * 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.descriptor; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Stream; + +import javax.validation.metadata.CascadableDescriptor; +import javax.validation.metadata.ConstraintDescriptor; +import javax.validation.metadata.ContainerDescriptor; +import javax.validation.metadata.ContainerElementTypeDescriptor; +import javax.validation.metadata.ElementDescriptor; +import javax.validation.metadata.GroupConversionDescriptor; +import javax.validation.metadata.PropertyDescriptor; + +import org.apache.bval.jsr.util.ToUnmodifiable; +import org.apache.bval.util.Validate; + +public abstract class ComposedD<D extends ElementD<?, ?>> implements ElementDescriptor { + + static abstract class ForCascadableContainer<D extends CascadableContainerD<?, ?>> extends ComposedD<D> + implements CascadableDescriptor, ContainerDescriptor { + + ForCascadableContainer(List<D> delegates) { + super(delegates); + } + + @Override + public Set<ContainerElementTypeDescriptor> getConstrainedContainerElementTypes() { + return delegates.stream().map(ContainerDescriptor::getConstrainedContainerElementTypes) + .flatMap(Collection::stream).collect(ToUnmodifiable.set()); + } + + @Override + public boolean isCascaded() { + return delegates.stream().anyMatch(CascadableDescriptor::isCascaded); + } + + @Override + public Set<GroupConversionDescriptor> getGroupConversions() { + return delegates.stream().map(CascadableDescriptor::getGroupConversions).flatMap(Collection::stream) + .collect(ToUnmodifiable.set()); + } + } + + static class ForProperty extends ComposedD.ForCascadableContainer<PropertyD<?>> implements PropertyDescriptor { + + ForProperty(List<PropertyD<?>> delegates) { + super(delegates); + } + + @Override + public String getPropertyName() { + return delegates.stream().map(PropertyDescriptor::getPropertyName).findFirst() + .orElseThrow(IllegalStateException::new); + } + } + + public static <T extends ElementD<?, ?>> Stream<T> unwrap(ElementDescriptor descriptor, Class<T> delegateType) { + final Stream<?> s; + + if (descriptor instanceof ComposedD<?>) { + s = ((ComposedD<?>) descriptor).delegates.stream() + // unwrap recursively: + .flatMap(d -> unwrap(d, delegateType)); + } else { + s = Stream.of(descriptor); + } + return s.map(delegateType::cast); + } + + protected final List<D> delegates; + + ComposedD(List<D> delegates) { + super(); + this.delegates = delegates; + + Validate.notNull(delegates, "delegates"); + Validate.isTrue(!delegates.isEmpty(), "At least one delegate is required"); + Validate.isTrue(delegates.stream().noneMatch(Objects::isNull), "null delegates not permitted"); + } + + @Override + public boolean hasConstraints() { + return delegates.stream().anyMatch(ElementDescriptor::hasConstraints); + } + + @Override + public Class<?> getElementClass() { + return delegates.stream().map(ElementDescriptor::getElementClass).findFirst() + .orElseThrow(IllegalStateException::new); + } + + @Override + public Set<ConstraintDescriptor<?>> getConstraintDescriptors() { + return delegates.stream().map(ElementDescriptor::getConstraintDescriptors).flatMap(Collection::stream) + .collect(ToUnmodifiable.set()); + } + + @Override + public ConstraintFinder findConstraints() { + return new Finder(this); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ComputeConstraintValidatorClass.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ComputeConstraintValidatorClass.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ComputeConstraintValidatorClass.java new file mode 100644 index 0000000..7eeefd2 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ComputeConstraintValidatorClass.java @@ -0,0 +1,183 @@ +package org.apache.bval.jsr.descriptor; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Array; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.WildcardType; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.validation.ConstraintDefinitionException; +import javax.validation.ConstraintValidator; +import javax.validation.UnexpectedTypeException; +import javax.validation.constraintvalidation.ValidationTarget; + +import org.apache.bval.jsr.ApacheValidatorFactory; +import org.apache.bval.jsr.ConstraintCached.ConstraintValidatorInfo; +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.bval.util.reflection.TypeUtils; +import org.apache.commons.weaver.privilizer.Privilizing; +import org.apache.commons.weaver.privilizer.Privilizing.CallTo; + +@Privilizing(@CallTo(Reflection.class)) +class ComputeConstraintValidatorClass<A extends Annotation> + implements Supplier<Class<? extends ConstraintValidator<A, ?>>> { + + private static class TypeWrapper { + final Class<?> componentType; + final int arrayDepth; + + TypeWrapper(Class<?> type) { + Class<?> c = type; + int d = 0; + while (Object[].class.isAssignableFrom(c)) { + d++; + c = c.getComponentType(); + } + this.componentType = c; + this.arrayDepth = d; + } + + Class<?> unwrap(Class<?> t) { + Exceptions.raiseUnless(t.isAssignableFrom(componentType), IllegalArgumentException::new, + "%s not assignable from %s", t, componentType); + if (arrayDepth == 0) { + return t; + } + return Array.newInstance(t, new int[arrayDepth]).getClass(); + } + } + + private static final String CV = ConstraintValidator.class.getSimpleName(); + private static final WildcardType UNBOUNDED = TypeUtils.wildcardType().build(); + + private static Class<?> getValidatedType(Class<? extends ConstraintValidator<?, ?>> validatorType) { + final Type result = TypeUtils.getTypeArguments(validatorType, ConstraintValidator.class) + .get(ConstraintValidator.class.getTypeParameters()[1]); + Exceptions.raiseUnless(isSupported(result), ConstraintDefinitionException::new, + "Validated type %s declared by %s %s is unsupported", result, CV, validatorType.getName()); + return TypeUtils.getRawType(result, null); + } + + private static boolean isSupported(Type validatedType) { + if (validatedType instanceof Class<?>) { + return true; + } + if (validatedType instanceof ParameterizedType) { + return Stream.of(((ParameterizedType) validatedType).getActualTypeArguments()) + .allMatch(arg -> TypeUtils.equals(arg, UNBOUNDED)); + } + return false; + } + + private final ApacheValidatorFactory validatorFactory; + private final Class<?> validatedType; + private final ValidationTarget validationTarget; + private final A constraint; + private final boolean composed; + + ComputeConstraintValidatorClass(ApacheValidatorFactory validatorFactory, ValidationTarget validationTarget, + A constraint, Class<?> validatedType) { + super(); + this.validatorFactory = Validate.notNull(validatorFactory, "validatorFactory"); + this.validationTarget = Validate.notNull(validationTarget, "validationTarget"); + this.constraint = Validate.notNull(constraint, "constraint"); + this.validatedType = Validate.notNull(validatedType, "validatedType"); + this.composed = validatorFactory.getAnnotationsManager().isComposed(constraint); + } + + @Override + public Class<? extends ConstraintValidator<A, ?>> get() { + @SuppressWarnings("unchecked") + final Class<A> constraintType = (Class<A>) constraint.annotationType(); + return findValidator(validatorFactory.getConstraintsCache().getConstraintValidatorInfo(constraintType)); + } + + private Class<? extends ConstraintValidator<A, ?>> findValidator(Set<ConstraintValidatorInfo<A>> infos) { + switch (validationTarget) { + case PARAMETERS: + return findCrossParameterValidator(infos); + case ANNOTATED_ELEMENT: + return findAnnotatedElementValidator(infos); + default: + return null; + } + } + + private Class<? extends ConstraintValidator<A, ?>> findCrossParameterValidator( + Set<ConstraintValidatorInfo<A>> infos) { + + final Set<ConstraintValidatorInfo<A>> set = + infos.stream().filter(info -> info.getSupportedTargets().contains(ValidationTarget.PARAMETERS)) + .collect(Collectors.toSet()); + + @SuppressWarnings("unchecked") + final Class<A> constraintType = (Class<A>) constraint.annotationType(); + + Exceptions.raiseIf(set.size() > 1 || !composed && set.isEmpty(), UnexpectedTypeException::new, + "%d cross-parameter %ss found for constraint type %s", set.size(), CV, constraintType); + + final Class<? extends ConstraintValidator<A, ?>> result = set.iterator().next().getType(); + Exceptions.raiseUnless(TypeUtils.isAssignable(Object[].class, getValidatedType(result)), + ConstraintDefinitionException::new, + "Cross-parameter %s %s does not support the validation of an object array", CV, result.getName()); + + return result; + } + + private Class<? extends ConstraintValidator<A, ?>> findAnnotatedElementValidator( + Set<ConstraintValidatorInfo<A>> infos) { + + final Map<Class<?>, Class<? extends ConstraintValidator<?, ?>>> validators = + infos.stream().filter(info -> info.getSupportedTargets().contains(ValidationTarget.ANNOTATED_ELEMENT)) + .map(ConstraintValidatorInfo::getType) + .collect(Collectors.toMap(ComputeConstraintValidatorClass::getValidatedType, Function.identity())); + + final Map<Type, Class<? extends ConstraintValidator<?, ?>>> candidates = new HashMap<>(); + + walkHierarchy().filter(validators::containsKey).forEach(type -> { + // if we haven't already found a candidate whose validated type + // is a subtype of the current evaluated type, save: + if (!candidates.keySet().stream().anyMatch(k -> TypeUtils.isAssignable(k, type))) { + candidates.put(type, validators.get(type)); + } + }); + final String cond; + switch (candidates.size()) { + case 1: + @SuppressWarnings("unchecked") + final Class<? extends ConstraintValidator<A, ?>> result = + (Class<? extends ConstraintValidator<A, ?>>) candidates.values().iterator().next(); + return result; + case 0: + if (composed) { + return null; + } + cond = "No compliant"; + break; + default: + cond = "> 1 maximally specific"; + break; + } + throw Exceptions.create(UnexpectedTypeException::new, "%s %s %s found for annotated element of type %s", cond, + constraint.annotationType().getName(), CV, TypeUtils.toString(validatedType)); + } + + // account for validated array types by unwrapping and rewrapping component + // type hierarchy: + private Stream<Class<?>> walkHierarchy() { + final TypeWrapper w = new TypeWrapper(Reflection.primitiveToWrapper(validatedType)); + Stream.Builder<Class<?>> hierarchy = Stream.builder(); + Reflection.hierarchy(w.componentType, Interfaces.INCLUDE).forEach(hierarchy); + return hierarchy.build().map(w::unwrap); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ConstraintD.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ConstraintD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ConstraintD.java new file mode 100644 index 0000000..0c1be1b --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ConstraintD.java @@ -0,0 +1,276 @@ +/* + * 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.descriptor; + +import java.lang.annotation.Annotation; +import java.lang.annotation.ElementType; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import javax.validation.ConstraintDeclarationException; +import javax.validation.ConstraintDefinitionException; +import javax.validation.ConstraintTarget; +import javax.validation.ConstraintValidator; +import javax.validation.Payload; +import javax.validation.ReportAsSingleViolation; +import javax.validation.UnexpectedTypeException; +import javax.validation.ValidationException; +import javax.validation.groups.Default; +import javax.validation.metadata.ConstraintDescriptor; +import javax.validation.metadata.Scope; +import javax.validation.metadata.ValidateUnwrappedValue; +import javax.validation.valueextraction.UnwrapByDefault; +import javax.validation.valueextraction.Unwrapping; +import javax.validation.valueextraction.Unwrapping.Skip; +import javax.validation.valueextraction.Unwrapping.Unwrap; +import javax.validation.valueextraction.ValueExtractor; + +import org.apache.bval.jsr.ApacheValidatorFactory; +import org.apache.bval.jsr.ConstraintAnnotationAttributes; +import org.apache.bval.jsr.ConstraintAnnotationAttributes.Worker; +import org.apache.bval.jsr.metadata.ContainerElementKey; +import org.apache.bval.jsr.metadata.Metas; +import org.apache.bval.jsr.util.AnnotationsManager; +import org.apache.bval.jsr.util.ToUnmodifiable; +import org.apache.bval.jsr.valueextraction.ValueExtractors; +import org.apache.bval.util.Exceptions; +import org.apache.bval.util.Lazy; +import org.apache.bval.util.Validate; +import org.apache.bval.util.reflection.TypeUtils; + +public class ConstraintD<A extends Annotation> implements ConstraintDescriptor<A> { + private static <T> Set<T> set(Supplier<T[]> array) { + return Stream.of(array.get()).collect(ToUnmodifiable.set()); + } + + private final A annotation; + private final Scope scope; + private final Metas<?> meta; + private final Class<?> validatedType; + + private final Lazy<Set<Class<?>>> groups = new Lazy<>(this::computeGroups); + + private final Set<Class<? extends Payload>> payload; + + private final Lazy<Boolean> reportAsSingle = + new Lazy<>(() -> getAnnotation().annotationType().isAnnotationPresent(ReportAsSingleViolation.class)); + + private final Lazy<ValidateUnwrappedValue> valueUnwrapping = new Lazy<>(this::computeValidateUnwrappedValue); + + private final Lazy<Map<String, Object>> attributes; + private final Lazy<Set<ConstraintDescriptor<?>>> composingConstraints; + private final Lazy<List<Class<? extends ConstraintValidator<A, ?>>>> constraintValidatorClasses; + private final Lazy<Class<? extends ConstraintValidator<A, ?>>> constraintValidatorClass; + + public ConstraintD(A annotation, Scope scope, Metas<?> meta, ApacheValidatorFactory validatorFactory) { + this.annotation = Validate.notNull(annotation, "annotation"); + this.scope = Validate.notNull(scope, "scope"); + this.meta = Validate.notNull(meta, "meta"); + this.payload = computePayload(); + this.validatedType = computeValidatedType(validatorFactory); + + attributes = new Lazy<>(() -> AnnotationsManager.readAttributes(annotation)); + + // retain no references to the validatorFactory; only wrap it in lazy + // suppliers + Validate.notNull(validatorFactory, "validatorFactory"); + composingConstraints = new Lazy<>(computeComposingConstraints(validatorFactory)); + constraintValidatorClasses = new Lazy<>(computeConstraintValidatorClasses(validatorFactory)); + + final Supplier<Class<? extends ConstraintValidator<A, ?>>> computeConstraintValidatorClass = + new ComputeConstraintValidatorClass<>(validatorFactory, meta.getValidationTarget(), annotation, + validatedType); + + constraintValidatorClass = new Lazy<>(computeConstraintValidatorClass); + } + + @Override + public A getAnnotation() { + return annotation; + } + + @Override + public Set<Class<?>> getGroups() { + return groups.get(); + } + + @Override + public Set<Class<? extends Payload>> getPayload() { + return payload; + } + + @Override + public List<Class<? extends ConstraintValidator<A, ?>>> getConstraintValidatorClasses() { + return constraintValidatorClasses.get(); + } + + @Override + public Map<String, Object> getAttributes() { + return attributes.get(); + } + + @Override + public Set<ConstraintDescriptor<?>> getComposingConstraints() { + return composingConstraints.get(); + } + + @Override + public boolean isReportAsSingleViolation() { + return reportAsSingle.get().booleanValue(); + } + + @Override + public String getMessageTemplate() { + final boolean required = true; + return read(ConstraintAnnotationAttributes.MESSAGE, required); + } + + @Override + public ConstraintTarget getValidationAppliesTo() { + return read(ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO); + } + + @Override + public ValidateUnwrappedValue getValueUnwrapping() { + return valueUnwrapping.get(); + } + + @Override + public <U> U unwrap(Class<U> type) throws ValidationException { + try { + return type.cast(this); + } catch (ClassCastException e) { + throw new ValidationException(e); + } + } + + public Scope getScope() { + return scope; + } + + public Class<?> getDeclaringClass() { + return meta.getDeclaringClass(); + } + + public ElementType getDeclaredOn() { + return meta.getElementType(); + } + + public Class<?> getValidatedType() { + return validatedType; + } + + public Class<? extends ConstraintValidator<A, ?>> getConstraintValidatorClass() { + return constraintValidatorClass.get(); + } + + private <T> T read(ConstraintAnnotationAttributes attr) { + return read(attr, false); + } + + private <T> T read(ConstraintAnnotationAttributes attr, boolean required) { + final Class<? extends Annotation> constraintType = annotation.annotationType(); + final Optional<T> result = + Optional.of(constraintType).map(attr::analyze).filter(Worker::isValid).map(w -> w.<T> read(annotation)); + + Exceptions.raiseIf(required && !result.isPresent(), ConstraintDefinitionException::new, + "Required attribute %s missing from constraint type %s", attr.getAttributeName(), constraintType); + + return result.orElse(null); + } + + private Supplier<Set<ConstraintDescriptor<?>>> computeComposingConstraints( + ApacheValidatorFactory validatorFactory) { + return () -> Stream.of(validatorFactory.getAnnotationsManager().getComposingConstraints(annotation)) + .map(c -> new ConstraintD<>(c, scope, meta, validatorFactory)) + .collect(ToUnmodifiable.set(LinkedHashSet::new)); + } + + @SuppressWarnings("unchecked") + private Supplier<List<Class<? extends ConstraintValidator<A, ?>>>> computeConstraintValidatorClasses( + ApacheValidatorFactory validatorFactory) { + return () -> validatorFactory.getConstraintsCache() + .getConstraintValidatorClasses((Class<A>) annotation.annotationType()); + } + + private ValidateUnwrappedValue computeValidateUnwrappedValue() { + final Set<Class<? extends Payload>> p = getPayload(); + final boolean unwrap = p.contains(Unwrap.class); + final boolean skip = p.contains(Skip.class); + if (unwrap) { + Validate.validState(!skip, "Cannot specify both %s and %s", Unwrap.class.getSimpleName(), + Skip.class.getSimpleName()); + return ValidateUnwrappedValue.UNWRAP; + } + return skip ? ValidateUnwrappedValue.SKIP : ValidateUnwrappedValue.DEFAULT; + } + + private Set<Class<?>> computeGroups() { + final boolean required = true; + final Class<?>[] groups = read(ConstraintAnnotationAttributes.GROUPS, required); + if (groups.length == 0) { + return Collections.singleton(Default.class); + } + return set(() -> groups); + } + + private Set<Class<? extends Payload>> computePayload() { + final boolean required = true; + final Set<Class<? extends Payload>> result = set(() -> read(ConstraintAnnotationAttributes.PAYLOAD, required)); + Exceptions.raiseIf(result.containsAll(Arrays.asList(Unwrapping.Unwrap.class, Unwrapping.Skip.class)), + ConstraintDeclarationException::new, + "Constraint %s declared at %s specifies conflicting value unwrapping hints", annotation, meta.getHost()); + return result; + } + + private Class<?> computeValidatedType(ApacheValidatorFactory validatorFactory) { + final Class<?> rawType = TypeUtils.getRawType(meta.getType(), null); + + Exceptions.raiseIf(rawType == null, UnexpectedTypeException::new, "Could not calculate validated type from %s", + meta.getType()); + + if (payload.contains(Unwrapping.Skip.class)) { + return rawType; + } + final ValueExtractor<?> valueExtractor = + validatorFactory.getValueExtractors().find(new ContainerElementKey(meta.getAnnotatedType(), null)); + + final boolean unwrap = payload.contains(Unwrapping.Unwrap.class); + + if (valueExtractor == null) { + Exceptions.raiseIf(unwrap, ConstraintDeclarationException::new, "No compatible %s found for %s", + ValueExtractor.class.getSimpleName(), meta.getType()); + } else { + @SuppressWarnings("unchecked") + final Class<? extends ValueExtractor<?>> extractorClass = + (Class<? extends ValueExtractor<?>>) valueExtractor.getClass(); + if (unwrap || extractorClass.isAnnotationPresent(UnwrapByDefault.class)) { + return ValueExtractors.getExtractedType(valueExtractor, meta.getType()); + } + } + return rawType; + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ConstructorD.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ConstructorD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ConstructorD.java new file mode 100644 index 0000000..b89d29c --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ConstructorD.java @@ -0,0 +1,41 @@ +/* + * 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.descriptor; + +import java.lang.reflect.Constructor; + +import javax.validation.metadata.ConstructorDescriptor; + +public class ConstructorD extends ExecutableD<Constructor<?>, MetadataReader.ForConstructor, ConstructorD> + implements ConstructorDescriptor { + + ConstructorD(MetadataReader.ForConstructor reader, BeanD parent) { + super(reader, parent); + } + + @Override + public Class<?> getElementClass() { + return getParent().getElementClass(); + } + + @Override + protected String nameOf(Constructor<?> e) { + return e.getDeclaringClass().getSimpleName(); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ContainerElementTypeD.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ContainerElementTypeD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ContainerElementTypeD.java new file mode 100644 index 0000000..7cacff3 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ContainerElementTypeD.java @@ -0,0 +1,119 @@ +/* + * 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.descriptor; + +import java.lang.reflect.AnnotatedType; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import javax.validation.ValidationException; +import javax.validation.metadata.ContainerElementTypeDescriptor; +import javax.validation.valueextraction.ValueExtractor; + +import org.apache.bval.jsr.GraphContext; +import org.apache.bval.jsr.metadata.ContainerElementKey; +import org.apache.bval.jsr.util.NodeImpl; +import org.apache.bval.util.Exceptions; +import org.apache.bval.util.Lazy; +import org.apache.bval.util.Validate; + +public class ContainerElementTypeD extends CascadableContainerD<CascadableContainerD<?, ?>, AnnotatedType> + implements ContainerElementTypeDescriptor { + + private static class Receiver implements ValueExtractor.ValueReceiver { + private final GraphContext context; + private Lazy<List<GraphContext>> result = new Lazy<>(ArrayList::new); + + Receiver(GraphContext context) { + super(); + this.context = context; + } + + @Override + public void value(String nodeName, Object object) { + addChild(new NodeImpl.PropertyNodeImpl(nodeName), object); + } + + @Override + public void iterableValue(String nodeName, Object object) { + final NodeImpl.PropertyNodeImpl node = new NodeImpl.PropertyNodeImpl(nodeName); + node.setInIterable(true); + addChild(node, object); + } + + @Override + public void indexedValue(String nodeName, int i, Object object) { + final NodeImpl.PropertyNodeImpl node = new NodeImpl.PropertyNodeImpl(nodeName); + node.setIndex(Integer.valueOf(i)); + addChild(node, object); + } + + @Override + public void keyedValue(String nodeName, Object key, Object object) { + final NodeImpl.PropertyNodeImpl node = new NodeImpl.PropertyNodeImpl(nodeName); + node.setKey(key); + addChild(node, object); + } + + private void addChild(NodeImpl node, Object value) { + result.get().add(context.child(node, value)); + } + } + + private final ContainerElementKey key; + + ContainerElementTypeD(ContainerElementKey key, MetadataReader.ForContainer<AnnotatedType> reader, + CascadableContainerD<?, ?> parent) { + super(reader, parent); + this.key = Validate.notNull(key, "key"); + } + + @Override + public Class<?> getContainerClass() { + return key.getContainerClass(); + } + + @Override + public Integer getTypeArgumentIndex() { + return Integer.valueOf(key.getTypeArgumentIndex()); + } + + public ContainerElementKey getKey() { + return key; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + protected Stream<GraphContext> readImpl(GraphContext context) throws Exception { + final ValueExtractor valueExtractor = context.getValidatorContext().getValueExtractors().find(key); + Exceptions.raiseIf(valueExtractor == null, ValidationException::new, "No %s found for %s", + ValueExtractor.class.getSimpleName(), key); + + final Receiver receiver = new Receiver(context); + try { + valueExtractor.extractValues(context.getValue(), receiver); + } catch (ValidationException e) { + throw e; + } catch (Exception e) { + throw new ValidationException(e); + } + return receiver.result.get().stream(); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/CrossParameterD.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/CrossParameterD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/CrossParameterD.java new file mode 100644 index 0000000..0d51800 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/CrossParameterD.java @@ -0,0 +1,18 @@ +package org.apache.bval.jsr.descriptor; + +import java.lang.reflect.Executable; + +import javax.validation.metadata.CrossParameterDescriptor; + +public class CrossParameterD<P extends ExecutableD<?, ?, P>, E extends Executable> + extends ElementD.NonRoot<P, E, MetadataReader.ForElement<E, ?>> implements CrossParameterDescriptor { + + protected CrossParameterD(MetadataReader.ForElement<E, ?> reader, P parent) { + super(reader, parent); + } + + @Override + public Class<?> getElementClass() { + return Object[].class; + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java new file mode 100644 index 0000000..21816d7 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java @@ -0,0 +1,74 @@ +/* + * 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.descriptor; + +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import javax.validation.metadata.BeanDescriptor; +import javax.validation.metadata.CascadableDescriptor; +import javax.validation.metadata.ElementDescriptor; + +import org.apache.bval.jsr.ApacheValidatorFactory; +import org.apache.bval.jsr.metadata.AnnotationBehaviorMergeStrategy; +import org.apache.bval.jsr.metadata.CompositeBuilder; +import org.apache.bval.jsr.metadata.HierarchyBuilder; +import org.apache.bval.jsr.metadata.MetadataBuilder; +import org.apache.bval.jsr.metadata.DualBuilder; +import org.apache.bval.jsr.metadata.ReflectionBuilder; +import org.apache.bval.util.Validate; + +public class DescriptorManager { + public static <D extends ElementDescriptor & CascadableDescriptor> boolean isConstrained(D descriptor) { + return descriptor.hasConstraints() || descriptor.isCascaded(); + } + + private final ApacheValidatorFactory validatorFactory; + private final ConcurrentMap<Class<?>, BeanD> beanDescriptors = new ConcurrentHashMap<>(); + private final ReflectionBuilder reflectionBuilder; + private final MetadataReader metadataReader; + + public DescriptorManager(ApacheValidatorFactory validatorFactory) { + super(); + this.validatorFactory = Validate.notNull(validatorFactory, "validatorFactory"); + this.reflectionBuilder = new ReflectionBuilder(validatorFactory); + this.metadataReader = new MetadataReader(validatorFactory); + } + + public BeanDescriptor getBeanDescriptor(Class<?> beanClass) { + Validate.notNull(beanClass, IllegalArgumentException::new, "beanClass"); + return beanDescriptors.computeIfAbsent(beanClass, k -> new BeanD(metadataReader.forBean(k, builder(k)))); + } + + private MetadataBuilder.ForBean builder(Class<?> beanClass) { + final MetadataBuilder.ForBean primaryBuilder = + new HierarchyBuilder(reflectionBuilder::forBean).forBean(beanClass); + + final MetadataBuilder.ForBean customBuilder = new HierarchyBuilder(this::customBuilder).forBean(beanClass); + + return customBuilder.isEmpty() ? primaryBuilder : DualBuilder.forBean(primaryBuilder, customBuilder); + } + + private MetadataBuilder.ForBean customBuilder(Class<?> beanClass) { + final List<MetadataBuilder.ForBean> customBuilders = + validatorFactory.getMetadataBuilders().getCustomBuilders(beanClass); + + return customBuilders.isEmpty() ? null : customBuilders.stream() + .collect(CompositeBuilder.with(AnnotationBehaviorMergeStrategy.consensus()).compose()); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java new file mode 100644 index 0000000..c139773 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java @@ -0,0 +1,122 @@ +/* + * 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.descriptor; + +import java.lang.annotation.ElementType; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.validation.metadata.ConstraintDescriptor; +import javax.validation.metadata.ElementDescriptor; + +import org.apache.bval.util.Validate; +import org.apache.bval.util.reflection.TypeUtils; + +public abstract class ElementD<E extends AnnotatedElement, R extends MetadataReader.ForElement<E, ?>> + implements ElementDescriptor { + + public static abstract class NonRoot<P extends ElementD<?, ?>, E extends AnnotatedElement, R extends MetadataReader.ForElement<E, ?>> + extends ElementD<E, R> { + + protected final P parent; + + protected NonRoot(R reader, P parent) { + super(reader); + this.parent = Validate.notNull(parent, "parent"); + } + + public P getParent() { + return parent; + } + + @Override + public final Type getGenericType() { + if (TypeUtils.containsTypeVariables(genericType)) { + final Map<TypeVariable<?>, Type> args = + TypeUtils.getTypeArguments(parent.getGenericType(), Object.class); + return TypeUtils.unrollVariables(args, genericType); + } + return genericType; + } + + @Override + final protected BeanD getBean() { + return parent.getBean(); + } + + @Override + public final List<Class<?>> getGroupSequence() { + return getBean().getGroupSequence(); + } + } + + protected final Type genericType; + + private final E target; + private final ElementType elementType; + private final Set<ConstraintD<?>> constraints; + + protected ElementD(R reader) { + super(); + Validate.notNull(reader, "reader"); + this.genericType = reader.meta.getType(); + this.target = reader.meta.getHost(); + this.elementType = reader.meta.getElementType(); + this.constraints = reader.getConstraints(); + } + + @Override + public final boolean hasConstraints() { + return !constraints.isEmpty(); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public final Set<ConstraintDescriptor<?>> getConstraintDescriptors() { + return (Set) constraints; + } + + @Override + public final ConstraintFinder findConstraints() { + return new Finder(this); + } + + public final ElementType getElementType() { + return elementType; + } + + public final E getTarget() { + return target; + } + + public abstract Type getGenericType(); + + public abstract List<Class<?>> getGroupSequence(); + + protected abstract BeanD getBean(); + + @Override + public String toString() { + return String.format("%s: %s", getClass().getSimpleName(), target); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ExecutableD.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ExecutableD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ExecutableD.java new file mode 100644 index 0000000..db3df6c --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ExecutableD.java @@ -0,0 +1,84 @@ +/* + * 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.descriptor; + +import java.lang.reflect.Executable; +import java.util.List; + +import javax.validation.metadata.CrossParameterDescriptor; +import javax.validation.metadata.ExecutableDescriptor; +import javax.validation.metadata.ParameterDescriptor; +import javax.validation.metadata.ReturnValueDescriptor; + +public abstract class ExecutableD<E extends Executable, R extends MetadataReader.ForExecutable<E, R>, SELF extends ExecutableD<E, R, SELF>> + extends ElementD.NonRoot<BeanD, E, R> implements ExecutableDescriptor { + + private final String name; + private final ReturnValueD<SELF, E> returnValue; + private final List<ParameterD<SELF>> parameters; + private final CrossParameterD<SELF, E> crossParameter; + + @SuppressWarnings("unchecked") + protected ExecutableD(R reader, BeanD parent) { + super(reader, parent); + + name = reader.meta.getName(); + + returnValue = reader.getReturnValueDescriptor((SELF) this); + parameters = reader.getParameterDescriptors((SELF) this); + crossParameter = reader.getCrossParameterDescriptor((SELF) this); + } + + @Override + public final String getName() { + return name; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public final List<ParameterDescriptor> getParameterDescriptors() { + return (List) parameters; + } + + @Override + public final CrossParameterDescriptor getCrossParameterDescriptor() { + return crossParameter; + } + + @Override + public final ReturnValueDescriptor getReturnValueDescriptor() { + return returnValue; + } + + @Override + public final boolean hasConstrainedParameters() { + return parameters.stream().anyMatch(this::isConstrained); + } + + @Override + public final boolean hasConstrainedReturnValue() { + return isConstrained(returnValue); + } + + protected abstract String nameOf(E e); + + private boolean isConstrained(CascadableContainerD<?, ?> child) { + return child.isCascaded() || child.hasConstraints(); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/Finder.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/Finder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/Finder.java new file mode 100644 index 0000000..c01e069 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/Finder.java @@ -0,0 +1,103 @@ +/* + * 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.descriptor; + +import java.lang.annotation.ElementType; +import java.util.EnumSet; +import java.util.Objects; +import java.util.Set; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.validation.groups.Default; +import javax.validation.metadata.ConstraintDescriptor; +import javax.validation.metadata.ElementDescriptor; +import javax.validation.metadata.ElementDescriptor.ConstraintFinder; +import javax.validation.metadata.Scope; + +import org.apache.bval.jsr.util.ToUnmodifiable; +import org.apache.bval.util.Validate; + +class Finder implements ConstraintFinder, Supplier<Stream<ConstraintD<?>>> { + private Predicate<ConstraintD<?>> groups = c -> true; + private Predicate<ConstraintD<?>> scope; + private Predicate<ConstraintD<?>> elements; + + private final ElementDescriptor owner; + + Finder(ElementDescriptor owner) { + this.owner = Validate.notNull(owner, "owner"); + } + + @Override + public ConstraintFinder unorderedAndMatchingGroups(Class<?>... groups) { + this.groups = c -> Stream.of(groups).anyMatch(t -> { + final Set<Class<?>> constraintGroups = c.getGroups(); + return constraintGroups.contains(t) + || constraintGroups.contains(Default.class) && c.getDeclaringClass().isAssignableFrom(t); + }); + return this; + } + + @Override + public ConstraintFinder lookingAt(Scope scope) { + this.scope = scope == Scope.HIERARCHY ? null : c -> c.getScope() == scope; + return this; + } + + @Override + public ConstraintFinder declaredOn(ElementType... types) { + this.elements = c -> Stream.of(types).filter(Objects::nonNull) + .collect(Collectors.toCollection(() -> EnumSet.noneOf(ElementType.class))).contains(c.getDeclaredOn()); + + return this; + } + + @Override + public Set<ConstraintDescriptor<?>> getConstraintDescriptors() { + return get().collect(ToUnmodifiable.set()); + } + + @Override + public Stream<ConstraintD<?>> get() { + return getConstraints().filter(filter()); + } + + @Override + public boolean hasConstraints() { + return getConstraints().anyMatch(filter()); + } + + private Stream<ConstraintD<?>> getConstraints() { + return owner.getConstraintDescriptors().stream().<ConstraintD<?>> map(c -> c.unwrap(ConstraintD.class)); + } + + private Predicate<ConstraintD<?>> filter() { + Predicate<ConstraintD<?>> result = groups; + if (scope != null) { + result = result.and(scope); + } + if (elements != null) { + result = result.and(elements); + } + return result; + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/GroupConversion.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/GroupConversion.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/GroupConversion.java new file mode 100644 index 0000000..9ef724e --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/GroupConversion.java @@ -0,0 +1,85 @@ +/* + * 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.descriptor; + +import java.util.Objects; +import java.util.Optional; + +import javax.validation.metadata.GroupConversionDescriptor; + +import org.apache.bval.util.Lazy; +import org.apache.bval.util.LazyInt; +import org.apache.bval.util.Validate; + +public class GroupConversion implements GroupConversionDescriptor { + public static class Builder { + private final Class<?> from; + + private Builder(Class<?> from) { + this.from = from; + } + + public GroupConversion to(Class<?> to) { + return new GroupConversion(from, to); + } + } + + public static Builder from(Class<?> from) { + return new Builder(from); + } + + private final Class<?> from; + private final Class<?> to; + private final LazyInt hashCode; + private final Lazy<String> toString; + + private GroupConversion(Class<?> from, Class<?> to) { + super(); + this.from = Validate.notNull(from, "from"); + this.to = Validate.notNull(to, "to"); + this.hashCode = new LazyInt(() -> Objects.hash(this.from, this.to)); + this.toString = new Lazy<>( + () -> String.format("%s from %s to %s", GroupConversion.class.getSimpleName(), this.from, this.to)); + } + + @Override + public Class<?> getFrom() { + return from; + } + + @Override + public Class<?> getTo() { + return to; + } + + @Override + public boolean equals(Object obj) { + return obj == this + || Optional.ofNullable(obj).filter(GroupConversion.class::isInstance).map(GroupConversion.class::cast) + .filter(gc -> Objects.equals(from, gc.from) && Objects.equals(to, gc.to)).isPresent(); + } + + @Override + public int hashCode() { + return hashCode.getAsInt(); + } + + @Override + public String toString() { + return toString.get(); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java new file mode 100644 index 0000000..c2f9f0c --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java @@ -0,0 +1,291 @@ +/* + * 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.descriptor; + +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.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import javax.validation.GroupDefinitionException; +import javax.validation.GroupSequence; +import javax.validation.ParameterNameProvider; +import javax.validation.groups.Default; +import javax.validation.metadata.PropertyDescriptor; +import javax.validation.metadata.Scope; + +import org.apache.bval.jsr.ApacheValidatorFactory; +import org.apache.bval.jsr.metadata.ContainerElementKey; +import org.apache.bval.jsr.metadata.EmptyBuilder; +import org.apache.bval.jsr.metadata.MetadataBuilder; +import org.apache.bval.jsr.metadata.Metas; +import org.apache.bval.jsr.metadata.Signature; +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.Validate; +import org.apache.bval.util.reflection.Reflection; + +class MetadataReader { + + class ForElement<E extends AnnotatedElement, B extends MetadataBuilder.ForElement<E>> { + final Metas<E> meta; + protected final B builder; + + ForElement(Metas<E> meta, B builder) { + super(); + this.meta = Validate.notNull(meta, "meta"); + this.builder = Validate.notNull(builder, "builder"); + } + + Set<ConstraintD<?>> getConstraints() { + return builder.getConstraintsByScope(meta).entrySet().stream() + .flatMap(e -> describe(e.getValue(), e.getKey(), meta)).collect(ToUnmodifiable.set()); + } + + private Stream<ConstraintD<?>> describe(Annotation[] constraints, Scope scope, Metas<?> meta) { + return Stream.of(constraints).map(c -> new ConstraintD<>(c, scope, meta, validatorFactory)); + } + } + + class ForBean extends MetadataReader.ForElement<Class<?>, MetadataBuilder.ForClass> { + private final MetadataBuilder.ForBean beanBuilder; + + ForBean(Metas<Class<?>> meta, MetadataBuilder.ForBean builder) { + super(meta, Validate.notNull(builder, "builder").getClass(meta)); + this.beanBuilder = builder; + } + + Map<String, PropertyDescriptor> getProperties(BeanD parent) { + final Map<String, List<PropertyD<?>>> properties = new LinkedHashMap<>(); + final Function<? super String, ? extends List<PropertyD<?>>> descriptorList = k -> new ArrayList<>(); + + beanBuilder.getFields(meta).forEach((f, builder) -> { + final Field fld = Reflection.find(meta.getHost(), t -> Reflection.getDeclaredField(t, f)); + properties.computeIfAbsent(f, descriptorList).add(new PropertyD.ForField( + new MetadataReader.ForContainer<>(new Metas.ForField(fld), builder), parent)); + }); + + beanBuilder.getGetters(meta).forEach((g, builder) -> { + final Method getter = Reflection.find(meta.getHost(), t -> { + return Stream.of(Reflection.getDeclaredMethods(t)).filter(Methods::isGetter) + .filter(m -> g.equals(Methods.propertyName(m))).findFirst().orElse(null); + }); + Exceptions.raiseIf(getter == null, IllegalStateException::new, + "Getter method for property %s not found", g); + + properties.computeIfAbsent(g, descriptorList).add(new PropertyD.ForMethod( + new MetadataReader.ForContainer<>(new Metas.ForMethod(getter), builder), parent)); + }); + return properties.entrySet().stream().collect(ToUnmodifiable.map(Map.Entry::getKey, e -> { + final List<PropertyD<?>> delegates = e.getValue(); + + if (delegates.size() == 1) { + return delegates.get(0); + } + final Set<PropertyD<?>> constrained = + delegates.stream().filter(DescriptorManager::isConstrained).collect(Collectors.toSet()); + if (constrained.isEmpty()) { + return delegates.get(0); + } + if (constrained.size() == 1) { + return constrained.iterator().next(); + } + return new ComposedD.ForProperty(delegates); + })); + } + + Map<Signature, MethodD> getMethods(BeanD parent) { + final Map<Signature, MetadataBuilder.ForExecutable<Method>> methodBuilders = beanBuilder.getMethods(meta); + if (methodBuilders.isEmpty()) { + return Collections.emptyMap(); + } + final Map<Signature, MethodD> result = new LinkedHashMap<>(); + + methodBuilders.forEach((sig, builder) -> { + final Method m = Reflection.find(meta.getHost(), + t -> Reflection.getDeclaredMethod(t, sig.getName(), sig.getParameterTypes())); + + result.put(sig, new MethodD(new MetadataReader.ForMethod(new Metas.ForMethod(m), builder), parent)); + }); + return Collections.unmodifiableMap(result); + } + + Map<Signature, ConstructorD> getConstructors(BeanD parent) { + final Map<Signature, MetadataBuilder.ForExecutable<Constructor<?>>> ctorBuilders = + beanBuilder.getConstructors(meta); + + if (ctorBuilders.isEmpty()) { + return Collections.emptyMap(); + } + final Map<Signature, ConstructorD> result = new LinkedHashMap<>(); + + ctorBuilders.forEach((sig, builder) -> { + final Constructor<?> c = Reflection.getDeclaredConstructor(meta.getHost(), sig.getParameterTypes()); + result.put(sig, + new ConstructorD(new MetadataReader.ForConstructor(new Metas.ForConstructor(c), builder), parent)); + }); + return Collections.unmodifiableMap(result); + } + + List<Class<?>> getGroupSequence() { + List<Class<?>> result = builder.getGroupSequence(meta); + if (result == null) { + // resolve group sequence/Default redefinition up class hierarchy: + final Class<?> superclass = meta.getHost().getSuperclass(); + if (superclass != null) { + // attempt to mock parent sequence intent by appending this type immediately after supertype: + result = ((ElementD<?, ?>) validatorFactory.getDescriptorManager().getBeanDescriptor(superclass)) + .getGroupSequence(); + if (result != null) { + result = new ArrayList<>(result); + result.add(result.indexOf(superclass) + 1, meta.getHost()); + } + } + } + if (result != null) { + Exceptions.raiseUnless(result.contains(meta.getHost()), GroupDefinitionException::new, + "@%s for %s must contain %<s", GroupSequence.class.getSimpleName(), meta.getHost()); + Exceptions.raiseIf(result.contains(Default.class), GroupDefinitionException::new, + "@%s for %s must not contain %s", GroupSequence.class.getSimpleName(), meta.getHost(), + Default.class.getName()); + } + return result; + } + } + + class ForContainer<E extends AnnotatedElement> extends ForElement<E, MetadataBuilder.ForContainer<E>> { + + ForContainer(Metas<E> meta, MetadataBuilder.ForContainer<E> builder) { + super(meta, builder); + } + + boolean isCascaded() { + return builder.isCascade(meta); + } + + Set<GroupConversion> getGroupConversions() { + return builder.getGroupConversions(meta); + } + + Set<ContainerElementTypeD> getContainerElementTypes(CascadableContainerD<?, ?> parent) { + final Map<ContainerElementKey, MetadataBuilder.ForContainer<AnnotatedType>> containerElementTypes = + builder.getContainerElementTypes(meta); + + if (containerElementTypes.isEmpty()) { + return Collections.emptySet(); + } + final Set<ContainerElementTypeD> result = + new TreeSet<>(Comparator.comparing(ContainerElementTypeD::getKey)); + + containerElementTypes.forEach((k, builder) -> { + result.add(new ContainerElementTypeD(k, + new MetadataReader.ForContainer<>(new Metas.ForContainerElement(meta, k), builder), parent)); + }); + return Collections.unmodifiableSet(result); + } + } + + abstract class ForExecutable<E extends Executable, SELF extends ForExecutable<E, SELF>> + extends ForElement<E, MetadataBuilder.ForElement<E>> { + private final MetadataBuilder.ForExecutable<E> executableBuilder; + + ForExecutable(Metas<E> meta, MetadataBuilder.ForExecutable<E> executableBuilder) { + super(meta, EmptyBuilder.instance().forElement()); + this.executableBuilder = Validate.notNull(executableBuilder, "executableBuilder"); + } + + <X extends ExecutableD<E, SELF, X>> List<ParameterD<X>> getParameterDescriptors(X parent) { + final Parameter[] parameters = meta.getHost().getParameters(); + + final List<String> parameterNames = + getParameterNames(validatorFactory.getParameterNameProvider(), meta.getHost()); + + final List<MetadataBuilder.ForContainer<Parameter>> builders = executableBuilder.getParameters(meta); + + return IntStream.range(0, parameters.length).mapToObj(i -> { + final Metas.ForParameter param = new Metas.ForParameter(parameters[i], parameterNames.get(i)); + return new ParameterD<>(param, i, new MetadataReader.ForContainer<>(param, builders.get(i)), parent); + }).collect(ToUnmodifiable.list()); + } + + <X extends ExecutableD<E, SELF, X>> CrossParameterD<X, E> getCrossParameterDescriptor(X parent) { + final Metas.ForCrossParameter<E> cp = new Metas.ForCrossParameter<>(meta); + return new CrossParameterD<>(new MetadataReader.ForElement<>(cp, executableBuilder.getCrossParameter(cp)), + parent); + } + + <X extends ExecutableD<E, SELF, X>> ReturnValueD<X, E> getReturnValueDescriptor(X parent) { + return new ReturnValueD<>(new MetadataReader.ForContainer<>(meta, executableBuilder.getReturnValue(meta)), + parent); + } + + abstract List<String> getParameterNames(ParameterNameProvider parameterNameProvider, E host); + } + + class ForMethod extends ForExecutable<Method, ForMethod> { + ForMethod(Metas<Method> meta, MetadataBuilder.ForExecutable<Method> builder) { + super(meta, builder); + } + + @Override + List<String> getParameterNames(ParameterNameProvider parameterNameProvider, Method host) { + return parameterNameProvider.getParameterNames(host); + } + } + + class ForConstructor extends ForExecutable<Constructor<?>, ForConstructor> { + + ForConstructor(Metas<Constructor<?>> meta, MetadataBuilder.ForExecutable<Constructor<?>> builder) { + super(meta, builder); + } + + @Override + List<String> getParameterNames(ParameterNameProvider parameterNameProvider, Constructor<?> host) { + return parameterNameProvider.getParameterNames(host); + } + } + + private final ApacheValidatorFactory validatorFactory; + + MetadataReader(ApacheValidatorFactory validatorFactory) { + super(); + this.validatorFactory = Validate.notNull(validatorFactory, "validatorFactory"); + } + + MetadataReader.ForBean forBean(Class<?> beanClass, MetadataBuilder.ForBean builder) { + return new MetadataReader.ForBean(new Metas.ForClass(beanClass), builder); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MethodD.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MethodD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MethodD.java new file mode 100644 index 0000000..647569d --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MethodD.java @@ -0,0 +1,49 @@ +/* + * 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.descriptor; + +import java.lang.reflect.Method; + +import javax.validation.metadata.MethodDescriptor; +import javax.validation.metadata.MethodType; + +import org.apache.bval.jsr.util.Methods; + +class MethodD extends ExecutableD<Method, MetadataReader.ForMethod, MethodD> implements MethodDescriptor { + private final MethodType methodType; + + MethodD(MetadataReader.ForMethod reader, BeanD parent) { + super(reader, parent); + methodType = Methods.isGetter(reader.meta.getHost()) ? MethodType.GETTER : MethodType.NON_GETTER; + } + + @Override + public Class<?> getElementClass() { + return getTarget().getReturnType(); + } + + MethodType getMethodType() { + return methodType; + } + + @Override + protected String nameOf(Method e) { + return e.getName(); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ParameterD.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ParameterD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ParameterD.java new file mode 100644 index 0000000..951eccb --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ParameterD.java @@ -0,0 +1,62 @@ +/* + * 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.descriptor; + +import java.lang.reflect.Parameter; + +import javax.validation.metadata.ParameterDescriptor; + +import org.apache.bval.jsr.metadata.Metas; +import org.apache.bval.util.Validate; +import org.apache.bval.util.reflection.TypeUtils; + +public class ParameterD<P extends ExecutableD<?, ?, P>> extends CascadableContainerD<P, Parameter> + implements ParameterDescriptor { + + private final int index; + private final String name; + private final Class<?> type; + + protected ParameterD(Metas.ForParameter meta, int index, MetadataReader.ForContainer<Parameter> reader, P parent) { + super(reader, parent); + + Validate.isTrue(index >= 0 && index < meta.getHost().getDeclaringExecutable().getParameterCount(), + "Invalid parameter index %d", index); + + this.index = index; + + name = reader.meta.getName(); + type = TypeUtils.getRawType(reader.meta.getType(), parent.getElementClass()); + } + + @Override + public Class<?> getElementClass() { + return type; + } + + @Override + public int getIndex() { + return index; + } + + @Override + public String getName() { + return name; + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/PropertyD.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/PropertyD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/PropertyD.java new file mode 100644 index 0000000..818e7e0 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/PropertyD.java @@ -0,0 +1,106 @@ +/* + * 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.descriptor; + +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.function.Supplier; +import java.util.stream.Stream; + +import javax.validation.metadata.PropertyDescriptor; + +import org.apache.bval.jsr.GraphContext; +import org.apache.bval.jsr.util.Methods; +import org.apache.bval.jsr.util.NodeImpl; +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 abstract class PropertyD<E extends AnnotatedElement> extends CascadableContainerD<BeanD, E> + implements PropertyDescriptor { + + static class ForField extends PropertyD<Field> { + + ForField(MetadataReader.ForContainer<Field> reader, BeanD parent) { + super(reader, parent); + } + + @Override + public String getPropertyName() { + return host.getName(); + } + + @Override + public Object getValue(Object parent) throws Exception { + final boolean mustUnset = Reflection.setAccessible(host, true); + try { + return host.get(parent); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException(e); + } finally { + if (mustUnset) { + Reflection.setAccessible(host, false); + } + } + } + } + + static class ForMethod extends PropertyD<Method> { + + ForMethod(MetadataReader.ForContainer<Method> reader, BeanD parent) { + super(reader, parent); + } + + @Override + public String getPropertyName() { + return Methods.propertyName(host); + } + + @Override + public Object getValue(Object parent) throws Exception { + final boolean mustUnset = Reflection.setAccessible(host, true); + try { + return host.invoke(parent); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new IllegalArgumentException(e); + } finally { + if (mustUnset) { + Reflection.setAccessible(host, false); + } + } + } + } + + protected final E host; + + protected PropertyD(MetadataReader.ForContainer<E> reader, BeanD parent) { + super(reader, parent); + this.host = reader.meta.getHost(); + } + + @Override + protected Stream<GraphContext> readImpl(GraphContext context) throws Exception { + final Supplier<NodeImpl> propertyNode = () -> new NodeImpl.PropertyNodeImpl(getPropertyName()); + final Object value = getValue(context.getValue()); + return Stream.of(context.child(propertyNode.get(), value)); + } + + public abstract Object getValue(Object parent) throws Exception; +} http://git-wip-us.apache.org/repos/asf/bval/blob/40ac09f7/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ReturnValueD.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ReturnValueD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ReturnValueD.java new file mode 100644 index 0000000..a2204fc --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ReturnValueD.java @@ -0,0 +1,36 @@ +/* + * 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.descriptor; + +import java.lang.reflect.Executable; + +import javax.validation.metadata.ReturnValueDescriptor; + +public class ReturnValueD<P extends ExecutableD<?, ?, P>, E extends Executable> extends CascadableContainerD<P, E> + implements ReturnValueDescriptor { + + ReturnValueD(MetadataReader.ForContainer<E> reader, P parent) { + super(reader, parent); + } + + @Override + public Class<?> getElementClass() { + return parent.getElementClass(); + } +}
