Repository: bval Updated Branches: refs/heads/bv2 4ed419e52 -> 54169ac59
ValueExtractor work for passing TCK ValidatorResolutionTest Project: http://git-wip-us.apache.org/repos/asf/bval/repo Commit: http://git-wip-us.apache.org/repos/asf/bval/commit/54169ac5 Tree: http://git-wip-us.apache.org/repos/asf/bval/tree/54169ac5 Diff: http://git-wip-us.apache.org/repos/asf/bval/diff/54169ac5 Branch: refs/heads/bv2 Commit: 54169ac5974b3ce1821fd266f152707bc86f3d30 Parents: 4ed419e Author: Matt Benson <[email protected]> Authored: Wed Mar 21 10:45:31 2018 -0500 Committer: Matt Benson <[email protected]> Committed: Wed Mar 21 10:45:31 2018 -0500 ---------------------------------------------------------------------- .../jsr/descriptor/ContainerElementTypeD.java | 64 +--------- .../org/apache/bval/jsr/job/ValidationJob.java | 88 +++++++++++-- .../bval/jsr/metadata/ContainerElementKey.java | 45 +++++-- .../java/org/apache/bval/jsr/metadata/Meta.java | 27 +--- .../apache/bval/jsr/metadata/XmlBuilder.java | 2 +- .../bval/jsr/valueextraction/ExtractValues.java | 108 ++++++++++++++++ .../jsr/valueextraction/ValueExtractors.java | 62 ++++++--- .../apache/bval/util/EmulatedAnnotatedType.java | 125 +++++++++++++++++++ .../jsr/metadata/ContainerElementKeyTest.java | 34 ++++- 9 files changed, 434 insertions(+), 121 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/bval/blob/54169ac5/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 index cdae6f0..f8abed2 100644 --- 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 @@ -19,70 +19,21 @@ 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.ConstraintDeclarationException; -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.jsr.util.PathImpl; +import org.apache.bval.jsr.valueextraction.ExtractValues; 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 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.ContainerElementNodeImpl(nodeName), object); - } - - @Override - public void iterableValue(String nodeName, Object object) { - final NodeImpl node = new NodeImpl.ContainerElementNodeImpl(nodeName); - node.setInIterable(true); - addChild(node, object); - } - - @Override - public void indexedValue(String nodeName, int i, Object object) { - final NodeImpl node = new NodeImpl.ContainerElementNodeImpl(nodeName); - node.setIndex(Integer.valueOf(i)); - addChild(node, object); - } - - @Override - public void keyedValue(String nodeName, Object key, Object object) { - final NodeImpl node = new NodeImpl.ContainerElementNodeImpl(nodeName); - node.setKey(key); - addChild(node, object); - } - - private void addChild(NodeImpl node, Object value) { - final PathImpl path = context.getPath(); - if (node.getName() != null) { - path.addNode(node.inContainer(key.getContainerClass(), key.getTypeArgumentIndex())); - } - result.get().add(context.child(path, value)); - } - } - private final ContainerElementKey key; ContainerElementTypeD(ContainerElementKey key, MetadataReader.ForContainer<AnnotatedType> reader, @@ -105,21 +56,12 @@ public class ContainerElementTypeD extends CascadableContainerD<CascadableContai return key; } - @SuppressWarnings({ "rawtypes", "unchecked" }) @Override protected Stream<GraphContext> readImpl(GraphContext context) throws Exception { - final ValueExtractor valueExtractor = context.getValidatorContext().getValueExtractors().find(key); + final ValueExtractor<?> valueExtractor = context.getValidatorContext().getValueExtractors().find(key); Exceptions.raiseIf(valueExtractor == null, ConstraintDeclarationException::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(); + return ExtractValues.extract(context, key, valueExtractor).stream(); } } http://git-wip-us.apache.org/repos/asf/bval/blob/54169ac5/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java index 2d4237c..3c74ce0 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java @@ -22,6 +22,7 @@ import java.lang.reflect.Array; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; @@ -32,11 +33,13 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentSkipListSet; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; +import javax.validation.ConstraintDeclarationException; import javax.validation.ConstraintValidator; import javax.validation.ConstraintViolation; import javax.validation.ElementKind; @@ -50,6 +53,8 @@ import javax.validation.metadata.CascadableDescriptor; import javax.validation.metadata.ContainerDescriptor; import javax.validation.metadata.ElementDescriptor.ConstraintFinder; import javax.validation.metadata.PropertyDescriptor; +import javax.validation.metadata.ValidateUnwrappedValue; +import javax.validation.valueextraction.ValueExtractor; import org.apache.bval.jsr.ApacheFactoryContext; import org.apache.bval.jsr.ConstraintViolationImpl; @@ -66,6 +71,8 @@ import org.apache.bval.jsr.metadata.ContainerElementKey; import org.apache.bval.jsr.util.NodeImpl; import org.apache.bval.jsr.util.PathImpl; import org.apache.bval.jsr.util.Proxies; +import org.apache.bval.jsr.valueextraction.ExtractValues; +import org.apache.bval.jsr.valueextraction.ValueExtractors; import org.apache.bval.util.Exceptions; import org.apache.bval.util.Lazy; import org.apache.bval.util.ObjectUtils; @@ -103,9 +110,52 @@ public abstract class ValidationJob<T> { abstract Object getBean(); - protected void validateDescriptorConstraints(Class<?> group, Consumer<ConstraintViolation<T>> sink) { + void validateDescriptorConstraints(Class<?> group, Consumer<ConstraintViolation<T>> sink) { constraintsFrom(descriptor.findConstraints().unorderedAndMatchingGroups(group)) - .forEach(c -> validate(c, sink)); + .forEach(c -> expand(c.getValueUnwrapping()).forEach(f -> f.validate(c, sink))); + } + + private Stream<Frame<D>> expand(ValidateUnwrappedValue valueUnwrapping) { + if (valueUnwrapping != ValidateUnwrappedValue.SKIP && context.getValue() != null) { + final Optional<Map.Entry<ContainerElementKey, ValueExtractor<?>>> valueExtractorAndAssociatedContainerElementKey = + findValueExtractorAndAssociatedContainerElementKey(context.getValue().getClass(), valueUnwrapping); + + if (valueExtractorAndAssociatedContainerElementKey.isPresent()) { + return ExtractValues + .extract(context, valueExtractorAndAssociatedContainerElementKey.get().getKey(), + valueExtractorAndAssociatedContainerElementKey.get().getValue()) + .stream().map(child -> new UnwrappedElementConstraintValidationPseudoFrame<>(this, child)); + } + } + return Stream.of(this); + } + + private Optional<Map.Entry<ContainerElementKey, ValueExtractor<?>>> findValueExtractorAndAssociatedContainerElementKey( + Class<?> containerClass, ValidateUnwrappedValue valueUnwrapping) { + final Map<ContainerElementKey, ValueExtractor<?>> m = new HashMap<>(); + final Predicate<ValueExtractor<?>> valueExtractorFilter = + x -> valueUnwrapping == ValidateUnwrappedValue.UNWRAP || ValueExtractors.isUnwrapByDefault(x); + + final ContainerElementKey nonGenericKey = new ContainerElementKey(containerClass, null); + + Optional.of(nonGenericKey).map(validatorContext.getValueExtractors()::find).filter(valueExtractorFilter) + .ifPresent(x -> m.put(nonGenericKey, x)); + + if (containerClass.getTypeParameters().length == 1) { + final ContainerElementKey genericKey = new ContainerElementKey(containerClass, Integer.valueOf(0)); + + Optional.of(genericKey).map(validatorContext.getValueExtractors()::find).filter(valueExtractorFilter) + .ifPresent(x -> m.put(genericKey, x)); + } + if (m.isEmpty()) { + Exceptions.raiseIf(valueUnwrapping == ValidateUnwrappedValue.UNWRAP, + ConstraintDeclarationException::new, "No %s found for %s", containerClass); + return Optional.empty(); + } + Exceptions.raiseIf(m.size() > 1, ConstraintDeclarationException::new, + "Found generic and non-generic %ss for %s", ValueExtractor.class.getSimpleName(), containerClass); + + return Optional.of(m.entrySet().iterator().next()); } @SuppressWarnings("unchecked") @@ -126,11 +176,11 @@ public abstract class ValidationJob<T> { // seen, ignore: return true; } + final ConstraintValidator constraintValidator = getConstraintValidator(constraint); + final ConstraintValidatorContextImpl<T> constraintValidatorContext = new ConstraintValidatorContextImpl<>(this, constraint); - final ConstraintValidator constraintValidator = getConstraintValidator(constraint); - final boolean valid; if (constraintValidator == null) { // null validator without exception implies composition: @@ -144,9 +194,9 @@ public abstract class ValidationJob<T> { } catch (Exception e) { throw new ValidationException(e); } - } - if (!valid) { - constraintValidatorContext.getRequiredViolations().forEach(sink); + if (!valid) { + constraintValidatorContext.getRequiredViolations().forEach(sink); + } } if (valid || !constraint.isReportAsSingleViolation()) { final boolean compositionValid = validateComposed(constraint, sink); @@ -395,6 +445,30 @@ public abstract class ValidationJob<T> { } } + private class UnwrappedElementConstraintValidationPseudoFrame<D extends ElementD<?, ?>> extends Frame<D> { + final Lazy<IllegalStateException> exc = new Lazy<>(() -> Exceptions.create(IllegalStateException::new, + "%s is not meant to participate in validation lifecycle", getClass())); + + UnwrappedElementConstraintValidationPseudoFrame(ValidationJob<T>.Frame<D> parent, GraphContext context) { + super(parent, parent.descriptor, context); + } + + @Override + void validateDescriptorConstraints(Class<?> group, Consumer<ConstraintViolation<T>> sink) { + throw exc.get(); + } + + @Override + void recurse(Class<?> group, Consumer<ConstraintViolation<T>> sink) { + throw exc.get(); + } + + @Override + Object getBean() { + return parent.getBean(); + } + } + protected static final TypeVariable<?> MAP_VALUE = Map.class.getTypeParameters()[1]; protected static final TypeVariable<?> ITERABLE_ELEMENT = Iterable.class.getTypeParameters()[0]; http://git-wip-us.apache.org/repos/asf/bval/blob/54169ac5/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ContainerElementKey.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ContainerElementKey.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ContainerElementKey.java index acf1676..d7b5b18 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ContainerElementKey.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ContainerElementKey.java @@ -19,6 +19,7 @@ package org.apache.bval.jsr.metadata; import java.lang.reflect.AnnotatedParameterizedType; import java.lang.reflect.AnnotatedType; import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.Collections; import java.util.Comparator; @@ -36,6 +37,7 @@ import javax.validation.valueextraction.ExtractedValue; import javax.validation.valueextraction.ValueExtractor; import javax.validation.valueextraction.ValueExtractorDefinitionException; +import org.apache.bval.util.EmulatedAnnotatedType; import org.apache.bval.util.Exceptions; import org.apache.bval.util.Lazy; import org.apache.bval.util.LazyInt; @@ -43,6 +45,7 @@ import org.apache.bval.util.Validate; import org.apache.bval.util.reflection.TypeUtils; public class ContainerElementKey implements Comparable<ContainerElementKey> { + private static Logger log = Logger.getLogger(ContainerElementKey.class.getName()); public static ContainerElementKey forValueExtractor(ValueExtractor<?> extractor) { @@ -57,10 +60,15 @@ public class ContainerElementKey implements Comparable<ContainerElementKey> { final AnnotatedType containerType = decl.getAnnotatedActualTypeArguments()[0]; if (containerType.isAnnotationPresent(ExtractedValue.class)) { - Exceptions.raiseIf(void.class.equals(containerType.getAnnotation(ExtractedValue.class).type()), - ValueExtractorDefinitionException::new, "%s does not specify %s type for %s", extractorType, - ExtractedValue.class.getSimpleName(), containerType); - result.get().add(new ContainerElementKey(containerType, null)); + final Class<?> extractedType = containerType.getAnnotation(ExtractedValue.class).type(); + Exceptions.raiseIf(void.class.equals(extractedType), ValueExtractorDefinitionException::new, + "%s does not specify %s type for %s", extractorType, ExtractedValue.class.getSimpleName(), + containerType); + result.get().add(new ContainerElementKey(containerType, null) { + public AnnotatedType getAnnotatedType() { + return EmulatedAnnotatedType.wrap(extractedType); + } + }); } Optional.of(containerType).filter(AnnotatedParameterizedType.class::isInstance) .map(AnnotatedParameterizedType.class::cast) @@ -105,6 +113,14 @@ public class ContainerElementKey implements Comparable<ContainerElementKey> { .getAnnotatedActualTypeArguments()[typeArgumentIndex.intValue()]; } + public ContainerElementKey(Class<?> containerClass, Integer typeArgumentIndex) { + Validate.notNull(containerClass, "containerClass"); + this.containerClass = containerClass; + this.typeArgumentIndex = validTypeArgumentIndex(typeArgumentIndex, containerClass); + this.annotatedType = typeArgumentIndex == null ? null + : EmulatedAnnotatedType.wrap(containerClass.getTypeParameters()[typeArgumentIndex.intValue()]); + } + public Class<?> getContainerClass() { return containerClass; } @@ -114,15 +130,14 @@ public class ContainerElementKey implements Comparable<ContainerElementKey> { } public AnnotatedType getAnnotatedType() { - return annotatedType; + return Optional.ofNullable(annotatedType).orElseThrow(UnsupportedOperationException::new); } @Override public boolean equals(Object obj) { return obj == this || Optional.ofNullable(obj).filter(ContainerElementKey.class::isInstance) - .map(ContainerElementKey.class::cast) - .filter( - cek -> Objects.equals(containerClass, cek.containerClass) && typeArgumentIndex == cek.typeArgumentIndex) + .map(ContainerElementKey.class::cast).filter(cek -> Objects.equals(containerClass, cek.containerClass) + && Objects.equals(typeArgumentIndex, cek.typeArgumentIndex)) .isPresent(); } @@ -139,7 +154,8 @@ public class ContainerElementKey implements Comparable<ContainerElementKey> { @Override public int compareTo(ContainerElementKey o) { return Comparator.comparing(ContainerElementKey::containerClassName) - .thenComparing(Comparator.comparing(ContainerElementKey::getTypeArgumentIndex)).compare(this, o); + .thenComparing(Comparator.nullsFirst(Comparator.comparing(ContainerElementKey::getTypeArgumentIndex))) + .compare(this, o); } public Set<ContainerElementKey> getAssignableKeys() { @@ -155,11 +171,12 @@ public class ContainerElementKey implements Comparable<ContainerElementKey> { } private void hierarchy(Consumer<ContainerElementKey> sink) { + final TypeVariable<?> var; if (typeArgumentIndex == null) { - return; + var = null; + } else { + var = containerClass.getTypeParameters()[typeArgumentIndex.intValue()]; } - final TypeVariable<?> var = containerClass.getTypeParameters()[typeArgumentIndex.intValue()]; - final Lazy<Set<ContainerElementKey>> round = new Lazy<>(LinkedHashSet::new); Stream .concat(Stream.of(containerClass.getAnnotatedSuperclass()), @@ -168,7 +185,8 @@ public class ContainerElementKey implements Comparable<ContainerElementKey> { .forEach(t -> { final AnnotatedType[] args = ((AnnotatedParameterizedType) t).getAnnotatedActualTypeArguments(); for (int i = 0; i < args.length; i++) { - if (args[i].getType().equals(var)) { + final Type boundArgumentType = args[i].getType(); + if (boundArgumentType instanceof Class<?> || boundArgumentType.equals(var)) { round.get().add(new ContainerElementKey(t, Integer.valueOf(i))); } } @@ -176,6 +194,7 @@ public class ContainerElementKey implements Comparable<ContainerElementKey> { round.optional().ifPresent(s -> { s.forEach(sink); + // recurse: s.forEach(k -> k.hierarchy(sink)); }); } http://git-wip-us.apache.org/repos/asf/bval/blob/54169ac5/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Meta.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Meta.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Meta.java index 2ec80d2..913c1bf 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Meta.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Meta.java @@ -18,7 +18,6 @@ */ package org.apache.bval.jsr.metadata; -import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.AnnotatedType; @@ -33,6 +32,7 @@ import java.util.Objects; import javax.validation.constraintvalidation.ValidationTarget; +import org.apache.bval.util.EmulatedAnnotatedType; import org.apache.bval.util.Lazy; import org.apache.bval.util.Validate; @@ -44,9 +44,11 @@ import org.apache.bval.util.Validate; public abstract class Meta<E extends AnnotatedElement> { public static class ForClass<T> extends Meta<Class<T>> { + private final AnnotatedType annotatedType; public ForClass(Class<T> host) { super(host, ElementType.TYPE); + this.annotatedType = EmulatedAnnotatedType.wrap(host); } @Override @@ -61,28 +63,7 @@ public abstract class Meta<E extends AnnotatedElement> { @Override public AnnotatedType getAnnotatedType() { - return new AnnotatedType() { - - @Override - public Annotation[] getDeclaredAnnotations() { - return getHost().getDeclaredAnnotations(); - } - - @Override - public Annotation[] getAnnotations() { - return getHost().getAnnotations(); - } - - @Override - public <A extends Annotation> A getAnnotation(Class<A> annotationClass) { - return getHost().getAnnotation(annotationClass); - } - - @Override - public Type getType() { - return getHost(); - } - }; + return annotatedType; } @Override http://git-wip-us.apache.org/repos/asf/bval/blob/54169ac5/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java index 26d5224..a9cb7f4 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java @@ -271,7 +271,7 @@ public class XmlBuilder { "Missing required type argument index for %s", host); typeArgumentIndex = Integer.valueOf(0); } - return new ContainerElementKey((AnnotatedParameterizedType) annotatedType, typeArgumentIndex); + return new ContainerElementKey(annotatedType, typeArgumentIndex); }, XmlBuilder.ForContainerElementType::new)); } Exceptions.raiseUnless(elements.isEmpty(), ValidationException::new, http://git-wip-us.apache.org/repos/asf/bval/blob/54169ac5/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ExtractValues.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ExtractValues.java b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ExtractValues.java new file mode 100644 index 0000000..cde4618 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ExtractValues.java @@ -0,0 +1,108 @@ +/* + * 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.valueextraction; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.validation.ValidationException; +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.jsr.util.PathImpl; +import org.apache.bval.util.Lazy; +import org.apache.bval.util.Validate; + +/** + * Utility class to extract values from a {@link GraphContext} using a {@link ValueExtractor}. + */ +public final class ExtractValues { + + private static class Receiver implements ValueExtractor.ValueReceiver { + private final GraphContext context; + private final ContainerElementKey containerElementKey; + private final Lazy<List<GraphContext>> result = new Lazy<>(ArrayList::new); + + Receiver(GraphContext context, ContainerElementKey containerElementKey) { + super(); + this.context = context; + this.containerElementKey = containerElementKey; + } + + @Override + public void value(String nodeName, Object object) { + addChild(new NodeImpl.ContainerElementNodeImpl(nodeName), object); + } + + @Override + public void iterableValue(String nodeName, Object object) { + final NodeImpl node = new NodeImpl.ContainerElementNodeImpl(nodeName); + node.setInIterable(true); + addChild(node, object); + } + + @Override + public void indexedValue(String nodeName, int i, Object object) { + final NodeImpl node = new NodeImpl.ContainerElementNodeImpl(nodeName); + node.setIndex(Integer.valueOf(i)); + addChild(node, object); + } + + @Override + public void keyedValue(String nodeName, Object key, Object object) { + final NodeImpl node = new NodeImpl.ContainerElementNodeImpl(nodeName); + node.setKey(key); + addChild(node, object); + } + + private void addChild(NodeImpl node, Object value) { + final PathImpl path = context.getPath(); + if (node.getName() != null) { + path.addNode(node.inContainer(containerElementKey.getContainerClass(), + containerElementKey.getTypeArgumentIndex())); + } + result.get().add(context.child(path, value)); + } + } + + private ExtractValues() { + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public static List<GraphContext> extract(GraphContext context, ContainerElementKey containerElementKey, + ValueExtractor<?> valueExtractor) { + Validate.notNull(context, "context"); + Validate.notNull(containerElementKey, "containerElementKey"); + if (valueExtractor != null) { + final Receiver receiver = new Receiver(context, containerElementKey); + try { + ((ValueExtractor) valueExtractor).extractValues(context.getValue(), receiver); + } catch (ValidationException e) { + throw e; + } catch (Exception e) { + throw new ValidationException(e); + } + return receiver.result.optional().orElse(Collections.emptyList()); + } + return Collections.singletonList(context); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/54169ac5/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java index 3adc541..77af34a 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java @@ -19,8 +19,7 @@ package org.apache.bval.jsr.valueextraction; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.util.Collection; +import java.lang.reflect.WildcardType; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -28,10 +27,14 @@ import java.util.Optional; import java.util.Properties; import java.util.Set; import java.util.function.BooleanSupplier; +import java.util.function.Function; +import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; +import javax.validation.ConstraintDeclarationException; +import javax.validation.valueextraction.UnwrapByDefault; import javax.validation.valueextraction.ValueExtractor; import javax.validation.valueextraction.ValueExtractorDeclarationException; import javax.validation.valueextraction.ValueExtractorDefinitionException; @@ -42,6 +45,7 @@ import org.apache.bval.util.Lazy; import org.apache.bval.util.StringUtils; 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; public class ValueExtractors { @@ -80,16 +84,29 @@ public class ValueExtractors { } public static Class<?> getExtractedType(ValueExtractor<?> extractor, Type target) { + final ContainerElementKey key = ContainerElementKey.forValueExtractor(extractor); Type result = key.getAnnotatedType().getType(); - if (result instanceof TypeVariable<?>) { - result = TypeUtils.getTypeArguments(target, key.getContainerClass()).get(result); + if (result instanceof WildcardType && key.getTypeArgumentIndex() != null) { + result = TypeUtils.getTypeArguments(target, key.getContainerClass()) + .get(key.getContainerClass().getTypeParameters()[key.getTypeArgumentIndex().intValue()]); } Exceptions.raiseUnless(result instanceof Class<?>, ValueExtractorDefinitionException::new, "%s did not resolve to a %s relative to %s", key, Class.class.getName(), target); return (Class<?>) result; } + public static boolean isUnwrapByDefault(ValueExtractor<?> valueExtractor) { + if (valueExtractor != null) { + for (Class<?> t : Reflection.hierarchy(valueExtractor.getClass(), Interfaces.INCLUDE)) { + if (t.isAnnotationPresent(UnwrapByDefault.class)) { + return true; + } + } + } + return false; + } + private static Stream<String> split(String s) { return Stream.of(StringUtils.split(s, ',')); } @@ -128,6 +145,10 @@ public class ValueExtractors { }).map(ValueExtractors::newInstance); } + private static boolean related(Class<?> c1, Class<?> c2) { + return c1.isAssignableFrom(c2) || c2.isAssignableFrom(c1); + } + private final Lazy<Map<ContainerElementKey, ValueExtractor<?>>> valueExtractors = new Lazy<>(HashMap::new); private final ValueExtractors parent; @@ -164,20 +185,31 @@ public class ValueExtractors { return allValueExtractors.get(key); } // search for assignable ContainerElementKey: - Set<ContainerElementKey> assignableKeys = key.getAssignableKeys(); - while (!assignableKeys.isEmpty()) { - final Optional<ValueExtractor<?>> found = assignableKeys.stream().filter(allValueExtractors::containsKey) - .<ValueExtractor<?>> map(allValueExtractors::get).findFirst(); - if (found.isPresent()) { - return found.get(); - } - assignableKeys = assignableKeys.stream().map(ContainerElementKey::getAssignableKeys) - .flatMap(Collection::stream).collect(Collectors.toSet()); + final Set<ContainerElementKey> assignableKeys = key.getAssignableKeys(); + if (assignableKeys.isEmpty()) { + return null; + } + final Map<ContainerElementKey, ValueExtractor<?>> candidateMap = + assignableKeys.stream().filter(allValueExtractors::containsKey) + .collect(Collectors.toMap(Function.identity(), allValueExtractors::get)); + + if (candidateMap.isEmpty()) { + return null; + } + if (candidateMap.size() > 1) { + final Set<Class<?>> containerTypes = + candidateMap.keySet().stream().map(ContainerElementKey::getContainerClass).collect(Collectors.toSet()); + + final boolean allRelated = containerTypes.stream().allMatch(quid -> containerTypes.stream() + .filter(Predicate.isEqual(quid).negate()).allMatch(quo -> related(quid, quo))); + + Exceptions.raiseUnless(allRelated, ConstraintDeclarationException::new, + "> 1 maximally specific %s found for %s", ValueExtractor.class.getSimpleName(), key); } - return null; + return candidateMap.values().iterator().next(); } - protected void populate(Supplier<Map<ContainerElementKey, ValueExtractor<?>>> target) { + private void populate(Supplier<Map<ContainerElementKey, ValueExtractor<?>>> target) { Optional.ofNullable(parent).ifPresent(p -> p.populate(target)); valueExtractors.optional().ifPresent(m -> target.get().putAll(m)); } http://git-wip-us.apache.org/repos/asf/bval/blob/54169ac5/bval-jsr/src/main/java/org/apache/bval/util/EmulatedAnnotatedType.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/util/EmulatedAnnotatedType.java b/bval-jsr/src/main/java/org/apache/bval/util/EmulatedAnnotatedType.java new file mode 100644 index 0000000..7df9655 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/util/EmulatedAnnotatedType.java @@ -0,0 +1,125 @@ +/* + * 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.util; + +import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; +import java.lang.reflect.AnnotatedParameterizedType; +import java.lang.reflect.AnnotatedType; +import java.lang.reflect.AnnotatedTypeVariable; +import java.lang.reflect.AnnotatedWildcardType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.lang.reflect.WildcardType; +import java.util.Optional; +import java.util.stream.Stream; + +public class EmulatedAnnotatedType<T extends Type> implements AnnotatedType { + private static class Parameterized extends EmulatedAnnotatedType<ParameterizedType> + implements AnnotatedParameterizedType { + + Parameterized(ParameterizedType wrapped) { + super(wrapped); + } + + @Override + public AnnotatedType[] getAnnotatedActualTypeArguments() { + return wrapArray(wrapped.getActualTypeArguments()); + } + } + + private static class Variable extends EmulatedAnnotatedType<TypeVariable<?>> implements AnnotatedTypeVariable { + + Variable(TypeVariable<?> wrapped) { + super(wrapped); + } + + @Override + public AnnotatedType[] getAnnotatedBounds() { + return wrapped.getAnnotatedBounds(); + } + } + + private static class Wildcard extends EmulatedAnnotatedType<WildcardType> implements AnnotatedWildcardType { + + Wildcard(WildcardType wrapped) { + super(wrapped); + } + + @Override + public AnnotatedType[] getAnnotatedLowerBounds() { + return wrapArray(wrapped.getLowerBounds()); + } + + @Override + public AnnotatedType[] getAnnotatedUpperBounds() { + return wrapArray(wrapped.getUpperBounds()); + } + } + + public static EmulatedAnnotatedType<?> wrap(Type type) { + if (type instanceof ParameterizedType) { + return new EmulatedAnnotatedType.Parameterized((ParameterizedType) type); + } + if (type instanceof TypeVariable<?>) { + return new EmulatedAnnotatedType.Variable((TypeVariable<?>) type); + } + if (type instanceof WildcardType) { + return new EmulatedAnnotatedType.Wildcard((WildcardType) type); + } + return new EmulatedAnnotatedType<>(type); + } + + private static EmulatedAnnotatedType<?>[] wrapArray(Type[] types) { + return Stream.of(types).map(EmulatedAnnotatedType::wrap).toArray(EmulatedAnnotatedType[]::new); + } + + private static final Annotation[] EMPTY_ANNOTATION_ARRAY = {}; + + protected final T wrapped; + private final Optional<AnnotatedElement> annotated; + + private EmulatedAnnotatedType(T wrapped) { + super(); + this.wrapped = Validate.notNull(wrapped); + this.annotated = + Optional.of(wrapped).filter(AnnotatedElement.class::isInstance).map(AnnotatedElement.class::cast); + } + + @Override + public <A extends Annotation> A getAnnotation(Class<A> annotationClass) { + return annotated.map(e -> e.getAnnotation(annotationClass)).orElse(null); + } + + @Override + public Annotation[] getAnnotations() { + return annotated.map(AnnotatedElement::getAnnotations).orElse(EMPTY_ANNOTATION_ARRAY); + } + + @Override + public Annotation[] getDeclaredAnnotations() { + return annotated.map(AnnotatedElement::getDeclaredAnnotations).orElse(EMPTY_ANNOTATION_ARRAY); + } + + @Override + public Type getType() { + return wrapped; + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/54169ac5/bval-jsr/src/test/java/org/apache/bval/jsr/metadata/ContainerElementKeyTest.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/test/java/org/apache/bval/jsr/metadata/ContainerElementKeyTest.java b/bval-jsr/src/test/java/org/apache/bval/jsr/metadata/ContainerElementKeyTest.java index b33b928..1fe2d9f 100644 --- a/bval-jsr/src/test/java/org/apache/bval/jsr/metadata/ContainerElementKeyTest.java +++ b/bval-jsr/src/test/java/org/apache/bval/jsr/metadata/ContainerElementKeyTest.java @@ -18,7 +18,9 @@ */ package org.apache.bval.jsr.metadata; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import java.lang.reflect.Field; import java.lang.reflect.TypeVariable; @@ -34,6 +36,9 @@ public class ContainerElementKeyTest { public List<String> strings; } + public static abstract class BoundListType implements List<String> { + } + private Field stringsField; @Before @@ -75,6 +80,33 @@ public class ContainerElementKeyTest { } @Test + public void testAssignableKeysWithExplicitBinding() { + final ContainerElementKey containerElementKey = new ContainerElementKey(BoundListType.class, null); + + final Iterator<ContainerElementKey> iterator = containerElementKey.getAssignableKeys().iterator(); + { + assertTrue(iterator.hasNext()); + final ContainerElementKey assignableKey = iterator.next(); + assertEquals(List.class, assignableKey.getContainerClass()); + assertEquals(0, assignableKey.getTypeArgumentIndex().intValue()); + } + { + assertTrue(iterator.hasNext()); + final ContainerElementKey assignableKey = iterator.next(); + assertEquals(Collection.class, assignableKey.getContainerClass()); + assertEquals(0, assignableKey.getTypeArgumentIndex().intValue()); + } + { + assertTrue(iterator.hasNext()); + final ContainerElementKey assignableKey = iterator.next(); + assertEquals(Iterable.class, assignableKey.getContainerClass()); + assertEquals(0, assignableKey.getTypeArgumentIndex().intValue()); + assertTrue(assignableKey.getAnnotatedType().getType() instanceof TypeVariable<?>); + } + assertFalse(iterator.hasNext()); + } + + @Test public void testTypeVariableInheritance() { final ContainerElementKey containerElementKey = new ContainerElementKey(stringsField.getAnnotatedType(), Integer.valueOf(0));
