You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bval.apache.org by mb...@apache.org on 2018/03/21 15:45:44 UTC

bval git commit: ValueExtractor work for passing TCK ValidatorResolutionTest

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 <mb...@apache.org>
Authored: Wed Mar 21 10:45:31 2018 -0500
Committer: Matt Benson <mb...@apache.org>
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));