value extraction work

Project: http://git-wip-us.apache.org/repos/asf/bval/repo
Commit: http://git-wip-us.apache.org/repos/asf/bval/commit/4dbe0936
Tree: http://git-wip-us.apache.org/repos/asf/bval/tree/4dbe0936
Diff: http://git-wip-us.apache.org/repos/asf/bval/diff/4dbe0936

Branch: refs/heads/bv2
Commit: 4dbe0936bcd2117e0f82c4495968c9d6434edb9e
Parents: 06f831d
Author: Matt Benson <[email protected]>
Authored: Thu Mar 29 16:55:47 2018 -0500
Committer: Matt Benson <[email protected]>
Committed: Thu Mar 29 16:55:47 2018 -0500

----------------------------------------------------------------------
 .../java/org/apache/bval/jsr/GraphContext.java  |  39 ++++-
 .../jsr/descriptor/CascadableContainerD.java    |  20 ---
 .../jsr/descriptor/ContainerElementTypeD.java   |  58 -------
 .../apache/bval/jsr/descriptor/PropertyD.java   |  21 ++-
 .../jsr/job/ConstraintValidatorContextImpl.java |   2 +-
 .../org/apache/bval/jsr/job/ValidateBean.java   |   4 +-
 .../apache/bval/jsr/job/ValidateParameters.java |   3 +-
 .../apache/bval/jsr/job/ValidateProperty.java   |   3 +-
 .../bval/jsr/job/ValidateReturnValue.java       |   4 +-
 .../org/apache/bval/jsr/job/ValidationJob.java  | 152 ++++++++++---------
 .../bval/jsr/metadata/ContainerElementKey.java  |  12 +-
 .../java/org/apache/bval/jsr/util/NodeImpl.java |  27 +++-
 .../java/org/apache/bval/jsr/util/PathImpl.java |   3 +
 .../bval/jsr/valueextraction/ExtractValues.java |   9 +-
 .../jsr/valueextraction/ValueExtractors.java    | 121 ++++++++++++---
 15 files changed, 275 insertions(+), 203 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/bval/blob/4dbe0936/bval-jsr/src/main/java/org/apache/bval/jsr/GraphContext.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/GraphContext.java 
b/bval-jsr/src/main/java/org/apache/bval/jsr/GraphContext.java
index 26350d6..3d40e75 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/GraphContext.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/GraphContext.java
@@ -18,11 +18,19 @@
  */
 package org.apache.bval.jsr;
 
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.Map;
+
 import javax.validation.Path;
+import javax.validation.ValidationException;
 
+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.Exceptions;
 import org.apache.bval.util.Validate;
+import org.apache.bval.util.reflection.TypeUtils;
 
 public class GraphContext {
 
@@ -64,9 +72,9 @@ public class GraphContext {
 
     public GraphContext child(Path p, Object value) {
         Validate.notNull(p, "Path");
-        final PathImpl impl = PathImpl.copy(p);
+        final PathImpl impl = PathImpl.of(p);
         Validate.isTrue(impl.isSubPathOf(path), "%s is not a subpath of %s", 
p, path);
-        return new GraphContext(validatorContext, impl, value, this);
+        return new GraphContext(validatorContext, impl == p ? 
PathImpl.copy(impl) : impl, value, this);
     }
 
     public boolean isRoot() {
@@ -92,4 +100,31 @@ public class GraphContext {
     public String toString() {
         return String.format("%s: %s at '%s'", getClass().getSimpleName(), 
value, path);
     }
+
+    public ContainerElementKey runtimeKey(ContainerElementKey key) {
+        final Class<?> containerClass = key.getContainerClass();
+        final Class<? extends Object> runtimeType = value.getClass();
+        if (!runtimeType.equals(containerClass)) {
+            
Exceptions.raiseUnless(containerClass.isAssignableFrom(runtimeType), 
ValidationException::new,
+                "Value %s is not assignment-compatible with %s", value, 
containerClass);
+
+            if (key.getTypeArgumentIndex() == null) {
+                return new ContainerElementKey(runtimeType, null);
+            }
+            final Map<TypeVariable<?>, Type> typeArguments = 
TypeUtils.getTypeArguments(runtimeType, containerClass);
+
+            Type type = 
typeArguments.get(containerClass.getTypeParameters()[key.getTypeArgumentIndex().intValue()]);
+
+            while (type instanceof TypeVariable<?>) {
+                final TypeVariable<?> var = (TypeVariable<?>) type;
+                final Type nextType = typeArguments.get(var);
+                if (nextType instanceof TypeVariable<?>) {
+                    type = nextType;
+                } else {
+                    return ContainerElementKey.forTypeVariable(var);
+                }
+            }
+        }
+        return key;
+    }
 }

http://git-wip-us.apache.org/repos/asf/bval/blob/4dbe0936/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
index a4e05fd..6894e34 100644
--- 
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
@@ -20,18 +20,14 @@ 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.groups.GroupConversion;
 import org.apache.bval.jsr.util.ToUnmodifiable;
-import org.apache.bval.util.Validate;
 import org.apache.bval.util.reflection.TypeUtils;
 
 public abstract class CascadableContainerD<P extends ElementD<?, ?>, E extends 
AnnotatedElement> extends
@@ -69,20 +65,4 @@ public abstract class CascadableContainerD<P extends 
ElementD<?, ?>, E extends A
         return 
containerElementTypes.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 e instanceof ValidationException ? (ValidationException) e : 
new ValidationException(e);
-        }
-    }
-
-    protected Stream<GraphContext> readImpl(GraphContext context) throws 
Exception {
-        throw new UnsupportedOperationException();
-    }
 }

http://git-wip-us.apache.org/repos/asf/bval/blob/4dbe0936/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 fc97df8..a64e0a0 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,33 +19,15 @@
 package org.apache.bval.jsr.descriptor;
 
 import java.lang.reflect.AnnotatedType;
-import java.lang.reflect.Type;
-import java.lang.reflect.TypeVariable;
-import java.util.Map;
-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.valueextraction.ExtractValues;
-import org.apache.bval.util.Exceptions;
-import org.apache.bval.util.ObjectUtils;
 import org.apache.bval.util.Validate;
-import org.apache.bval.util.reflection.TypeUtils;
 
 public class ContainerElementTypeD extends 
CascadableContainerD<CascadableContainerD<?, ?>, AnnotatedType>
     implements ContainerElementTypeDescriptor {
 
-    private static ContainerElementKey toContainerElementKey(TypeVariable<?> 
var) {
-        final Class<?> container = (Class<?>) var.getGenericDeclaration();
-        final int argIndex = 
ObjectUtils.indexOf(container.getTypeParameters(), var);
-        return new ContainerElementKey(container, Integer.valueOf(argIndex));
-    }
-
     private final ContainerElementKey key;
 
     ContainerElementTypeD(ContainerElementKey key, 
MetadataReader.ForContainer<AnnotatedType> reader,
@@ -67,44 +49,4 @@ public class ContainerElementTypeD extends 
CascadableContainerD<CascadableContai
     public ContainerElementKey getKey() {
         return key;
     }
-
-    @Override
-    protected Stream<GraphContext> readImpl(GraphContext context) throws 
Exception {
-        final ContainerElementKey runtimeKey = runtimeKey(context.getValue());
-        final ValueExtractor<?> valueExtractor =
-            
context.getValidatorContext().getValueExtractors().find(runtimeKey);
-
-        if (valueExtractor == null) {
-            Exceptions.raise(ConstraintDeclarationException::new, "No %s found 
for %s",
-                ValueExtractor.class.getSimpleName(), key);
-        }
-        return ExtractValues.extract(context, key, valueExtractor).stream();
-    }
-
-    private ContainerElementKey runtimeKey(Object value) {
-        final Class<?> containerClass = key.getContainerClass();
-        final Class<? extends Object> runtimeType = value.getClass();
-        if (!runtimeType.equals(containerClass)) {
-            
Exceptions.raiseUnless(containerClass.isAssignableFrom(runtimeType), 
ValidationException::new,
-                "Value %s is not assignment-compatible with %s", value, 
containerClass);
-
-            if (key.getTypeArgumentIndex() == null) {
-                return new ContainerElementKey(runtimeType, null);
-            }
-            final Map<TypeVariable<?>, Type> typeArguments = 
TypeUtils.getTypeArguments(runtimeType, containerClass);
-
-            Type type = 
typeArguments.get(containerClass.getTypeParameters()[key.getTypeArgumentIndex().intValue()]);
-
-            while (type instanceof TypeVariable<?>) {
-                final TypeVariable<?> var = (TypeVariable<?>) type;
-                final Type nextType = typeArguments.get(var);
-                if (nextType instanceof TypeVariable<?>) {
-                    type = nextType;
-                } else {
-                    return toContainerElementKey(var);
-                }
-            }
-        }
-        return key;
-    }
 }

http://git-wip-us.apache.org/repos/asf/bval/blob/4dbe0936/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
index 54a69f0..69a482e 100644
--- 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
@@ -22,11 +22,13 @@ import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.util.stream.Stream;
 
+import javax.validation.ValidationException;
 import javax.validation.metadata.PropertyDescriptor;
 
 import org.apache.bval.jsr.GraphContext;
 import org.apache.bval.jsr.util.Methods;
 import org.apache.bval.jsr.util.PathImpl;
+import org.apache.bval.util.Validate;
 import org.apache.bval.util.reflection.Reflection;
 import org.apache.commons.weaver.privilizer.Privilizing;
 import org.apache.commons.weaver.privilizer.Privilizing.CallTo;
@@ -94,12 +96,19 @@ public abstract class PropertyD<E extends AnnotatedElement> 
extends CascadableCo
         this.host = reader.meta.getHost();
     }
 
-    @Override
-    protected Stream<GraphContext> readImpl(GraphContext context) throws 
Exception {
-        final Object value = getValue(context.getValue());
-        final PathImpl p = PathImpl.copy(context.getPath());
-        p.addProperty(getPropertyName());
-        return Stream.of(context.child(p, value));
+    public final Stream<GraphContext> read(GraphContext context) {
+        Validate.notNull(context);
+        if (context.getValue() == null) {
+            return Stream.empty();
+        }
+        try {
+            final Object value = getValue(context.getValue());
+            final PathImpl p = PathImpl.copy(context.getPath());
+            p.addProperty(getPropertyName());
+            return Stream.of(context.child(p, value));
+        } catch (Exception e) {
+            throw e instanceof ValidationException ? (ValidationException) e : 
new ValidationException(e);
+        }
     }
 
     public abstract Object getValue(Object parent) throws Exception;

http://git-wip-us.apache.org/repos/asf/bval/blob/4dbe0936/bval-jsr/src/main/java/org/apache/bval/jsr/job/ConstraintValidatorContextImpl.java
----------------------------------------------------------------------
diff --git 
a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ConstraintValidatorContextImpl.java
 
b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ConstraintValidatorContextImpl.java
index 07c0c96..a822aa8 100644
--- 
a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ConstraintValidatorContextImpl.java
+++ 
b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ConstraintValidatorContextImpl.java
@@ -165,7 +165,7 @@ public class ConstraintValidatorContextImpl<T> implements 
ConstraintValidatorCon
     }
 
     @SuppressWarnings({ "unchecked", "rawtypes" })
-    public void addError(String messageTemplate, Path propertyPath) {
+    public void addError(String messageTemplate, PathImpl propertyPath) {
         violations.get().add(((ValidationJob) 
frame.getJob()).createViolation(messageTemplate, this, propertyPath));
     }
 

http://git-wip-us.apache.org/repos/asf/bval/blob/4dbe0936/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateBean.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateBean.java 
b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateBean.java
index f533314..bd20548 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateBean.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateBean.java
@@ -18,8 +18,6 @@
  */
 package org.apache.bval.jsr.job;
 
-import javax.validation.Path;
-
 import org.apache.bval.jsr.ApacheFactoryContext;
 import org.apache.bval.jsr.ConstraintViolationImpl;
 import org.apache.bval.jsr.GraphContext;
@@ -50,7 +48,7 @@ public final class ValidateBean<T> extends ValidationJob<T> {
 
     @Override
     ConstraintViolationImpl<T> createViolation(String messageTemplate, String 
message,
-        ConstraintValidatorContextImpl<T> context, Path propertyPath) {
+        ConstraintValidatorContextImpl<T> context, PathImpl propertyPath) {
         return new ConstraintViolationImpl<>(messageTemplate, message, bean,
             context.getFrame().getBean(), propertyPath, 
context.getFrame().context.getValue(),
             context.getConstraintDescriptor(), getRootBeanClass(),

http://git-wip-us.apache.org/repos/asf/bval/blob/4dbe0936/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateParameters.java
----------------------------------------------------------------------
diff --git 
a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateParameters.java 
b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateParameters.java
index 0e2b30c..777ff1a 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateParameters.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateParameters.java
@@ -28,7 +28,6 @@ import java.util.stream.IntStream;
 
 import javax.validation.ConstraintViolation;
 import javax.validation.ParameterNameProvider;
-import javax.validation.Path;
 import javax.validation.constraintvalidation.ValidationTarget;
 import javax.validation.metadata.ExecutableDescriptor;
 
@@ -181,7 +180,7 @@ public abstract class ValidateParameters<E extends 
Executable, T> extends Valida
 
     @Override
     ConstraintViolationImpl<T> createViolation(String messageTemplate, String 
message,
-        ConstraintValidatorContextImpl<T> context, Path propertyPath) {
+        ConstraintValidatorContextImpl<T> context, PathImpl propertyPath) {
         return new ConstraintViolationImpl<T>(messageTemplate, message, 
getRootBean(), context.getFrame().getBean(),
             propertyPath, context.getFrame().context.getValue(), 
context.getConstraintDescriptor(), getRootBeanClass(),
             
context.getConstraintDescriptor().unwrap(ConstraintD.class).getDeclaredOn(), 
null, parameterValues);

http://git-wip-us.apache.org/repos/asf/bval/blob/4dbe0936/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateProperty.java
----------------------------------------------------------------------
diff --git 
a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateProperty.java 
b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateProperty.java
index ce33840..11b6338 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateProperty.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateProperty.java
@@ -31,7 +31,6 @@ import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
 import javax.validation.ConstraintViolation;
-import javax.validation.Path;
 import javax.validation.metadata.BeanDescriptor;
 import javax.validation.metadata.CascadableDescriptor;
 import javax.validation.metadata.ContainerDescriptor;
@@ -515,7 +514,7 @@ public final class ValidateProperty<T> extends 
ValidationJob<T> {
 
     @Override
     ConstraintViolationImpl<T> createViolation(String messageTemplate, String 
message,
-        ConstraintValidatorContextImpl<T> context, Path propertyPath) {
+        ConstraintValidatorContextImpl<T> context, PathImpl propertyPath) {
         return new ConstraintViolationImpl<>(messageTemplate, message, 
rootBean, context.getFrame().getBean(),
             propertyPath, context.getFrame().context.getValue(), 
context.getConstraintDescriptor(), rootBeanClass,
             
context.getConstraintDescriptor().unwrap(ConstraintD.class).getDeclaredOn(), 
null, null);

http://git-wip-us.apache.org/repos/asf/bval/blob/4dbe0936/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java
----------------------------------------------------------------------
diff --git 
a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java 
b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java
index 8ffd5f5..f8d030b 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java
@@ -21,8 +21,6 @@ import java.lang.reflect.Executable;
 import java.lang.reflect.Method;
 import java.lang.reflect.Type;
 
-import javax.validation.Path;
-
 import org.apache.bval.jsr.ApacheFactoryContext;
 import org.apache.bval.jsr.ConstraintViolationImpl;
 import org.apache.bval.jsr.GraphContext;
@@ -125,7 +123,7 @@ public abstract class ValidateReturnValue<E extends 
Executable, T> extends Valid
 
     @Override
     ConstraintViolationImpl<T> createViolation(String messageTemplate, String 
message,
-        ConstraintValidatorContextImpl<T> context, Path propertyPath) {
+        ConstraintValidatorContextImpl<T> context, PathImpl propertyPath) {
         return new ConstraintViolationImpl<>(messageTemplate, message, 
getRootBean(), context.getFrame().getBean(),
             propertyPath, context.getFrame().context.getValue(), 
context.getConstraintDescriptor(), getRootBeanClass(),
             
context.getConstraintDescriptor().unwrap(ConstraintD.class).getDeclaredOn(), 
returnValue, null);

http://git-wip-us.apache.org/repos/asf/bval/blob/4dbe0936/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 4385724..68af377 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,7 +22,6 @@ 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;
@@ -33,13 +32,11 @@ 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;
@@ -55,7 +52,6 @@ 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.UnwrapByDefault;
 import javax.validation.valueextraction.ValueExtractor;
 
 import org.apache.bval.jsr.ApacheFactoryContext;
@@ -118,55 +114,25 @@ public abstract class ValidationJob<T> {
 
         void validateDescriptorConstraints(Class<?> group, 
Consumer<ConstraintViolation<T>> sink) {
             
constraintsFrom(descriptor.findConstraints().unorderedAndMatchingGroups(group))
-                .forEach(c -> expand(c.getValueUnwrapping()).forEach(f -> 
f.validate(c, sink)));
+                .forEach(c -> unwrap(c.getValueUnwrapping()).forEach(f -> 
f.validate(c, sink)));
         }
 
-        private Stream<Frame<D>> expand(ValidateUnwrappedValue 
valueUnwrapping) {
+        private Stream<Frame<D>> unwrap(ValidateUnwrappedValue 
valueUnwrapping) {
             if (valueUnwrapping != ValidateUnwrappedValue.SKIP && 
context.getValue() != null) {
-                final Optional<Map.Entry<ContainerElementKey, 
ValueExtractor<?>>> valueExtractorAndAssociatedContainerElementKey =
-                    
findValueExtractorAndAssociatedContainerElementKey(context.getValue().getClass(),
 valueUnwrapping);
+                final Optional<ValueExtractors.UnwrappingInfo> 
valueExtractorAndAssociatedContainerElementKey =
+                        validatorContext.getValueExtractors().
+                    findUnwrappingInfo(context.getValue().getClass(), 
valueUnwrapping);
 
                 if 
(valueExtractorAndAssociatedContainerElementKey.isPresent()) {
                     return ExtractValues
-                        .extract(context, 
valueExtractorAndAssociatedContainerElementKey.get().getKey(),
-                            
valueExtractorAndAssociatedContainerElementKey.get().getValue())
+                        .extract(context, 
valueExtractorAndAssociatedContainerElementKey.get().containerElementKey,
+                            
valueExtractorAndAssociatedContainerElementKey.get().valueExtractor)
                         .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()) {
-                if (valueUnwrapping == ValidateUnwrappedValue.UNWRAP) {
-                    Exceptions.raise(ConstraintDeclarationException::new, "No 
%s found for %s",
-                        ValueExtractor.class.getSimpleName(), containerClass);
-                }
-                return Optional.empty();
-            }
-            if (m.size() > 1) {
-                Exceptions.raise(ConstraintDeclarationException::new, "Found 
generic and non-generic %ss for %s",
-                    ValueExtractor.class.getSimpleName(), containerClass);
-            }
-            return Optional.of(m.entrySet().iterator().next());
-        }
-
         @SuppressWarnings("unchecked")
         private Stream<ConstraintD<?>> constraintsFrom(ConstraintFinder 
finder) {
             // our ConstraintFinder implementation is a Stream supplier; 
reference without exposing it beyond its
@@ -263,30 +229,16 @@ public abstract class ValidationJob<T> {
         }
 
         private Class<?> computeValidatedType(ConstraintD<?> constraint) {
-            final Class<?> elementClass = descriptor.getElementClass();
-
-            if (constraint.getValueUnwrapping() == 
ValidateUnwrappedValue.SKIP) {
-                return elementClass;
+            if (context.getValue() != null) {
+                return context.getValue().getClass();
             }
-            final ValueExtractor<?> valueExtractor =
-                validatorContext.getValueExtractors().find(new 
ContainerElementKey(elementClass, null));
+            final Class<?> elementClass = descriptor.getElementClass();
 
-            final boolean unwrap = constraint.getValueUnwrapping() == 
ValidateUnwrappedValue.UNWRAP;
+            final Optional<Class<?>> extractedType =
+                
validatorContext.getValueExtractors().findUnwrappingInfo(elementClass, 
constraint.getValueUnwrapping())
+                    .map(info -> 
ValueExtractors.getExtractedType(info.valueExtractor, elementClass));
 
-            if (valueExtractor == null) {
-                if (unwrap) {
-                    Exceptions.raise(ConstraintDeclarationException::new, "No 
compatible %s found for %s",
-                        ValueExtractor.class.getSimpleName(), elementClass);
-                }
-            } else {
-                @SuppressWarnings("unchecked")
-                final Class<? extends ValueExtractor<?>> extractorClass =
-                    (Class<? extends ValueExtractor<?>>) 
valueExtractor.getClass();
-                if (unwrap || 
extractorClass.isAnnotationPresent(UnwrapByDefault.class)) {
-                    return ValueExtractors.getExtractedType(valueExtractor, 
elementClass);
-                }
-            }
-            return elementClass;
+            return extractedType.orElse(elementClass);
         }
 
         private Stream<Class<?>> expand(Class<?> group) {
@@ -368,12 +320,7 @@ public abstract class ValidationJob<T> {
         }
 
         protected void recurseSingleExpandedGroup(Class<?> group, 
Consumer<ConstraintViolation<T>> sink) {
-            @SuppressWarnings({ "unchecked", "rawtypes" })
-            final Stream<ContainerElementTypeD> containerElements = 
descriptor.getConstrainedContainerElementTypes()
-                .stream().flatMap(d -> ComposedD.unwrap(d, (Class) 
ContainerElementTypeD.class));
-
-            containerElements.flatMap(d -> d.read(context).map(child -> new 
ContainerElementFrame(this, d, child)))
-                .forEach(f -> f.process(group, sink));
+            processContainerElements(group, sink);
 
             if (!descriptor.isCascaded()) {
                 return;
@@ -393,6 +340,30 @@ public abstract class ValidationJob<T> {
                 .map(context -> new BeanFrame<>(this, context)).forEach(b -> 
b.process(group, sink));
         }
 
+        private void processContainerElements(Class<?> group, 
Consumer<ConstraintViolation<T>> sink) {
+            if (context.getValue() == null) {
+                return;
+            }
+            // handle spec dichotomy: declared type for constraints; runtime 
type for cascades. Bypass #process()
+            descriptor.getConstrainedContainerElementTypes().stream()
+                .flatMap(d -> ComposedD.unwrap(d, 
ContainerElementTypeD.class)).forEach(d -> {
+                    if 
(!d.findConstraints().unorderedAndMatchingGroups(group).getConstraintDescriptors().isEmpty())
 {
+                        final ValueExtractor<?> declaredTypeValueExtractor =
+                            
context.getValidatorContext().getValueExtractors().find(d.getKey());
+                        ExtractValues.extract(context, d.getKey(), 
declaredTypeValueExtractor).stream()
+                            .filter(e -> !e.isRecursive()).map(e -> new 
ContainerElementConstraintsFrame(this, d, e))
+                            .forEach(f -> 
f.validateDescriptorConstraints(group, sink));
+                    }
+                    if (d.isCascaded() || 
!d.getConstrainedContainerElementTypes().isEmpty()) {
+                        final ValueExtractor<?> runtimeTypeValueExtractor =
+                            
context.getValidatorContext().getValueExtractors().find(context.runtimeKey(d.getKey()));
+                        ExtractValues.extract(context, d.getKey(), 
runtimeTypeValueExtractor).stream()
+                            .filter(e -> !e.isRecursive()).map(e -> new 
ContainerElementCascadeFrame(this, d, e))
+                            .forEach(f -> f.recurse(group, sink));
+                    }
+                });
+        }
+
         protected GraphContext getMultiplexContext() {
             return context;
         }
@@ -457,23 +428,48 @@ public abstract class ValidationJob<T> {
         }
     }
 
-    private class ContainerElementFrame extends 
SproutFrame<ContainerElementTypeD> {
+    private class ContainerElementConstraintsFrame extends 
SproutFrame<ContainerElementTypeD> {
 
-        ContainerElementFrame(ValidationJob<T>.Frame<?> parent, 
ContainerElementTypeD descriptor,
+        ContainerElementConstraintsFrame(ValidationJob<T>.Frame<?> parent, 
ContainerElementTypeD descriptor,
             GraphContext context) {
             super(parent, descriptor, context);
         }
+    
+        @Override
+        void recurse(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
+        }
+    }
+
+    private class ContainerElementCascadeFrame extends 
SproutFrame<ContainerElementTypeD> {
+
+        ContainerElementCascadeFrame(ValidationJob<T>.Frame<?> parent, 
ContainerElementTypeD descriptor,
+            GraphContext context) {
+            super(parent, descriptor, context);
+        }
+
+        @Override
+        void validateDescriptorConstraints(Class<?> group, 
Consumer<ConstraintViolation<T>> sink) {
+        }
 
         @Override
         protected GraphContext getMultiplexContext() {
             final PathImpl path = context.getPath();
-            final NodeImpl leafNode = path.getLeafNode();
 
-            final NodeImpl newLeaf;
+            GraphContext ancestor = context.getParent();
+            Validate.validState(ancestor!= null, "Expected parent context");
 
+            final NodeImpl leafNode = path.getLeafNode();
+            
+            final NodeImpl newLeaf;
+            
             if (leafNode.getKind() == ElementKind.CONTAINER_ELEMENT) {
                 // recurse using elided path:
                 path.removeLeafNode();
+
+                while (!path.equals(ancestor.getPath())) {
+                    ancestor = ancestor.getParent();
+                    Validate.validState(ancestor!= null, "Expected parent 
context");
+                }
                 newLeaf = new NodeImpl.PropertyNodeImpl(leafNode);
                 newLeaf.setName(null);
             } else {
@@ -483,7 +479,7 @@ public abstract class ValidationJob<T> {
             }
             path.addNode(newLeaf);
 
-            return context.getParent().child(path, context.getValue());
+            return ancestor.child(path, context.getValue());
         }
     }
 
@@ -579,12 +575,18 @@ public abstract class ValidationJob<T> {
     }
 
     final ConstraintViolationImpl<T> createViolation(String messageTemplate, 
ConstraintValidatorContextImpl<T> context,
-        Path propertyPath) {
+        PathImpl propertyPath) {
+        if (!propertyPath.isRootPath()) {
+            final NodeImpl leafNode = propertyPath.getLeafNode();
+            if (leafNode.getName() == null && !leafNode.isInIterable()) {
+                propertyPath.removeLeafNode();
+            }
+        }
         return createViolation(messageTemplate, interpolate(messageTemplate, 
context), context, propertyPath);
     }
 
     abstract ConstraintViolationImpl<T> createViolation(String 
messageTemplate, String message,
-        ConstraintValidatorContextImpl<T> context, Path propertyPath);
+        ConstraintValidatorContextImpl<T> context, PathImpl propertyPath);
 
     protected abstract Frame<?> computeBaseFrame();
 

http://git-wip-us.apache.org/repos/asf/bval/blob/4dbe0936/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 529ccda..2d7102b 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
@@ -41,6 +41,7 @@ import org.apache.bval.util.EmulatedAnnotatedType;
 import org.apache.bval.util.Exceptions;
 import org.apache.bval.util.Lazy;
 import org.apache.bval.util.LazyInt;
+import org.apache.bval.util.ObjectUtils;
 import org.apache.bval.util.Validate;
 import org.apache.bval.util.reflection.TypeUtils;
 
@@ -89,6 +90,12 @@ public class ContainerElementKey implements 
Comparable<ContainerElementKey> {
             .orElseThrow(() -> new 
ValueExtractorDefinitionException(extractorType.getName())).iterator().next();
     }
 
+    public static ContainerElementKey forTypeVariable(TypeVariable<?> var) {
+        final Class<?> container = (Class<?>) var.getGenericDeclaration();
+        final int argIndex = 
ObjectUtils.indexOf(container.getTypeParameters(), var);
+        return new ContainerElementKey(container, Integer.valueOf(argIndex));
+    }
+
     private static Integer validTypeArgumentIndex(Integer typeArgumentIndex, 
Class<?> containerClass) {
         if (typeArgumentIndex != null) {
             final int i = typeArgumentIndex.intValue();
@@ -154,8 +161,9 @@ public class ContainerElementKey implements 
Comparable<ContainerElementKey> {
 
     @Override
     public int compareTo(ContainerElementKey o) {
-        return Comparator.comparing(ContainerElementKey::containerClassName)
-            
.thenComparing(Comparator.nullsFirst(Comparator.comparing(ContainerElementKey::getTypeArgumentIndex)))
+        return Comparator
+            
.nullsFirst(Comparator.comparing(ContainerElementKey::containerClassName)
+                
.thenComparing(Comparator.nullsFirst(Comparator.comparing(ContainerElementKey::getTypeArgumentIndex))))
             .compare(this, o);
     }
 

http://git-wip-us.apache.org/repos/asf/bval/blob/4dbe0936/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java 
b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java
index 1d49a2a..9f7b0c3 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java
@@ -48,12 +48,18 @@ public abstract class NodeImpl implements Path.Node, 
Serializable {
     private static final long serialVersionUID = 1L;
 
     /**
-     * Comparator for any path {@link Node}.
+     * Comparator for any path {@link Node}. For iterable nodes with no, or 
{@code null}, key and index values
+     * the left operand is always treated as less than the right.
      */
     public static final Comparator<Path.Node> NODE_COMPARATOR =
         nullsFirst(comparing(Node::getName, 
nullsFirst(naturalOrder())).thenComparing(NodeImpl::compareIterability)
             .thenComparing(NodeImpl::compareSpecificNodeInfo));
 
+    private static final Comparator<Path.Node> NODE_EQUALITY_COMPARATOR =
+        nullsFirst(comparing(Node::getName, nullsFirst(naturalOrder()))
+            .thenComparing((o1, o2) -> NodeImpl.compareIterability(o1, o2, 
false))
+            .thenComparing(NodeImpl::compareSpecificNodeInfo));
+
     private static final Comparator<Class<?>> CLASS_COMPARATOR = 
Comparator.nullsFirst(
         Comparator.<Class<?>, Boolean> 
comparing(Class::isPrimitive).reversed().thenComparing(Class::getName));
 
@@ -130,16 +136,27 @@ public abstract class NodeImpl implements Path.Node, 
Serializable {
     }
 
     private static int compareIterability(Node quid, Node quo) {
+        final boolean strict = true;
+        return compareIterability(quid, quo, strict);
+    }
+
+    private static int compareIterability(Node quid, Node quo, boolean strict) 
{
         if (quid.isInIterable()) {
             if (quo.isInIterable()) {
                 if (quid.getKey() != null) {
                     return Comparator.comparing(Node::getKey, 
KEY_COMPARATOR).compare(quid, quo);
                 }
-                if (quid.getIndex() == null) {
-                    // this method cannot consistently order iterables without 
key or index; the first argument is
-                    // always assumed to be less:
+                if (quo.getKey() != null) {
                     return -1;
                 }
+                if (quid.getIndex() == null) {
+                    if (strict) {
+                        // this method cannot consistently order iterables 
without key or index; the first argument is
+                        // always assumed to be less:
+                        return -1;
+                    }
+                    return quo.getIndex() == null ? 0 : -1;
+                }
                 return quo.getIndex() == null ? 1 : 
quid.getIndex().compareTo(quo.getIndex());
             }
             return 1;
@@ -336,7 +353,7 @@ public abstract class NodeImpl implements Path.Node, 
Serializable {
         if (o == null || !getClass().equals(o.getClass())) {
             return false;
         }
-        return NODE_COMPARATOR.compare(this, (NodeImpl) o) == 0;
+        return NODE_EQUALITY_COMPARATOR.compare(this, (NodeImpl) o) == 0;
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/bval/blob/4dbe0936/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathImpl.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathImpl.java 
b/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathImpl.java
index 3289a47..54ad138 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathImpl.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathImpl.java
@@ -41,6 +41,9 @@ public class PathImpl implements Path, Serializable {
 
     private static final long serialVersionUID = 1L;
 
+    /**
+     * @see NodeImpl#NODE_COMPARATOR
+     */
     public static final Comparator<Path> PATH_COMPARATOR = 
Comparators.comparingIterables(NodeImpl.NODE_COMPARATOR);
 
     static final String PROPERTY_PATH_SEPARATOR = ".";

http://git-wip-us.apache.org/repos/asf/bval/blob/4dbe0936/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
index cde4618..01295c6 100644
--- 
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
@@ -29,6 +29,7 @@ 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.Exceptions;
 import org.apache.bval.util.Lazy;
 import org.apache.bval.util.Validate;
 
@@ -76,10 +77,8 @@ public final class ExtractValues {
 
         private void addChild(NodeImpl node, Object value) {
             final PathImpl path = context.getPath();
-            if (node.getName() != null) {
-                
path.addNode(node.inContainer(containerElementKey.getContainerClass(),
-                    containerElementKey.getTypeArgumentIndex()));
-            }
+            path.addNode(
+                node.inContainer(containerElementKey.getContainerClass(), 
containerElementKey.getTypeArgumentIndex()));
             result.get().add(context.child(path, value));
         }
     }
@@ -93,6 +92,8 @@ public final class ExtractValues {
         Validate.notNull(context, "context");
         Validate.notNull(containerElementKey, "containerElementKey");
         if (valueExtractor != null) {
+            Exceptions.raiseIf(context.getValue() == null, 
IllegalStateException::new,
+                "Cannot extract values from null");
             final Receiver receiver = new Receiver(context, 
containerElementKey);
             try {
                 ((ValueExtractor) 
valueExtractor).extractValues(context.getValue(), receiver);

http://git-wip-us.apache.org/repos/asf/bval/blob/4dbe0936/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 2ebc2c7..f635c92 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,9 +19,12 @@ 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.lang.reflect.WildcardType;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Optional;
@@ -37,6 +40,7 @@ import java.util.stream.Collectors;
 import java.util.stream.Stream;
 
 import javax.validation.ConstraintDeclarationException;
+import javax.validation.metadata.ValidateUnwrappedValue;
 import javax.validation.valueextraction.UnwrapByDefault;
 import javax.validation.valueextraction.ValueExtractor;
 import javax.validation.valueextraction.ValueExtractorDeclarationException;
@@ -45,6 +49,7 @@ import 
javax.validation.valueextraction.ValueExtractorDefinitionException;
 import org.apache.bval.jsr.metadata.ContainerElementKey;
 import org.apache.bval.util.Exceptions;
 import org.apache.bval.util.Lazy;
+import org.apache.bval.util.ObjectUtils;
 import org.apache.bval.util.StringUtils;
 import org.apache.bval.util.Validate;
 import org.apache.bval.util.reflection.Reflection;
@@ -59,6 +64,17 @@ public class ValueExtractors {
         EXCEPTION, OVERWRITE;
     }
 
+    public static class UnwrappingInfo {
+        public final ContainerElementKey containerElementKey;
+        public final ValueExtractor<?> valueExtractor;
+
+        private UnwrappingInfo(ContainerElementKey containerElementKey, 
ValueExtractor<?> valueExtractor) {
+            super();
+            this.containerElementKey = containerElementKey;
+            this.valueExtractor = valueExtractor;
+        }
+    }
+
     public static final ValueExtractors EMPTY =
         new ValueExtractors(null, OnDuplicateContainerElementKey.EXCEPTION, 
Collections.emptyMap());
 
@@ -151,12 +167,27 @@ public class ValueExtractors {
         }).map(ValueExtractors::newInstance);
     }
 
-    private static boolean related(Class<?> c1, Class<?> c2) {
-        return c1.isAssignableFrom(c2) || c2.isAssignableFrom(c1);
+    private static <T> Optional<T> maximallySpecific(Collection<T> candidates, 
Function<? super T, Class<?>> toType) {
+        final Collection<T> result;
+        if (candidates.size() > 1) {
+            result = new HashSet<>();
+            for (T candidate : candidates) {
+                final Class<?> candidateType = toType.apply(candidate);
+                if 
(candidates.stream().filter(Predicate.isEqual(candidate).negate()).map(toType)
+                    .allMatch(t -> t.isAssignableFrom(candidateType))) {
+                    result.add(candidate);
+                }
+            }
+        } else {
+            result = candidates;
+        }
+        return result.size() == 1 ? Optional.of(result.iterator().next()) : 
Optional.empty();
     }
 
     private final ValueExtractors parent;
     private final Lazy<Map<ContainerElementKey, ValueExtractor<?>>> 
valueExtractors = new Lazy<>(TreeMap::new);
+    private final Lazy<Set<ValueExtractors>> children = new 
Lazy<>(HashSet::new);
+    private final Lazy<Map<ContainerElementKey, ValueExtractor<?>>> 
searchCache = new Lazy<>(HashMap::new);
     private final OnDuplicateContainerElementKey 
onDuplicateContainerElementKey;
 
     public ValueExtractors() {
@@ -183,7 +214,9 @@ public class ValueExtractors {
     }
 
     public ValueExtractors createChild(OnDuplicateContainerElementKey 
onDuplicateContainerElementKey) {
-        return new ValueExtractors(this, onDuplicateContainerElementKey);
+        final ValueExtractors child = new ValueExtractors(this, 
onDuplicateContainerElementKey);
+        children.get().add(child);
+        return child;
     }
 
     public void add(ValueExtractor<?> extractor) {
@@ -205,6 +238,7 @@ public class ValueExtractors {
         } else {
             m.put(key, extractor);
         }
+        children.optional().ifPresent(s -> 
s.stream().forEach(ValueExtractors::clearCache));
     }
 
     public Map<ContainerElementKey, ValueExtractor<?>> getValueExtractors() {
@@ -214,37 +248,84 @@ public class ValueExtractors {
     }
 
     public ValueExtractor<?> find(ContainerElementKey key) {
+        final Optional<ValueExtractor<?>> cacheHit = 
searchCache.optional().map(m -> m.get(key));
+        if (cacheHit.isPresent()) {
+            return cacheHit.get();
+        }
         final Map<ContainerElementKey, ValueExtractor<?>> allValueExtractors = 
getValueExtractors();
         if (allValueExtractors.containsKey(key)) {
             return allValueExtractors.get(key);
         }
-        // search for assignable ContainerElementKey:
-        final Set<ContainerElementKey> assignableKeys = 
key.getAssignableKeys();
-        if (assignableKeys.isEmpty()) {
-            return null;
+        final Map<ValueExtractor<?>, ContainerElementKey> candidates = Stream
+            .concat(Stream.of(key), 
key.getAssignableKeys().stream()).filter(allValueExtractors::containsKey).collect(
+                Collectors.toMap(allValueExtractors::get, Function.identity(), 
(quid, quo) -> quo, LinkedHashMap::new));
+
+        final Optional<ValueExtractor<?>> result =
+            maximallySpecific(candidates.keySet(), ve -> 
candidates.get(ve).getContainerClass());
+        if (result.isPresent()) {
+            searchCache.get().put(key, result.get());
+            return result.get();
         }
-        final Map<ContainerElementKey, ValueExtractor<?>> candidateMap =
-            
assignableKeys.stream().filter(allValueExtractors::containsKey).collect(
-                Collectors.toMap(Function.identity(), allValueExtractors::get, 
(quid, quo) -> quo, LinkedHashMap::new));
+        throw Exceptions.create(ConstraintDeclarationException::new, "Could 
not determine %s for %s",
+            ValueExtractor.class.getSimpleName(), key);
+    }
 
-        if (candidateMap.isEmpty()) {
-            return null;
+    public Optional<UnwrappingInfo> findUnwrappingInfo(Class<?> containerClass,
+        ValidateUnwrappedValue valueUnwrapping) {
+        if (valueUnwrapping == ValidateUnwrappedValue.SKIP) {
+            return Optional.empty();
         }
-        if (candidateMap.size() > 1) {
-            final Set<Class<?>> containerTypes =
-                
candidateMap.keySet().stream().map(ContainerElementKey::getContainerClass).collect(Collectors.toSet());
+        final Map<ContainerElementKey, ValueExtractor<?>> allValueExtractors = 
getValueExtractors();
+
+        final Set<UnwrappingInfo> unwrapping = 
allValueExtractors.entrySet().stream()
+            .filter(e -> 
e.getKey().getContainerClass().isAssignableFrom(containerClass))
+            .map(e -> new UnwrappingInfo(e.getKey(), 
e.getValue())).collect(Collectors.toSet());
 
-            final boolean allRelated = containerTypes.stream().allMatch(quid 
-> containerTypes.stream()
-                .filter(Predicate.isEqual(quid).negate()).allMatch(quo -> 
related(quid, quo)));
+        final Optional<UnwrappingInfo> result =
+            maximallySpecific(unwrapping, u -> 
u.containerElementKey.getContainerClass());
 
-            Exceptions.raiseUnless(allRelated, 
ConstraintDeclarationException::new,
-                "> 1 maximally specific %s found for %s", f -> 
f.args(ValueExtractor.class.getSimpleName(), key));
+        if (result.isPresent()) {
+            if (valueUnwrapping == ValidateUnwrappedValue.UNWRAP || 
isUnwrapByDefault(result.get().valueExtractor)) {
+                return result
+                    .map(u -> new UnwrappingInfo(translateTo(containerClass, 
u.containerElementKey), u.valueExtractor));
+            }
+        } else if (valueUnwrapping == ValidateUnwrappedValue.UNWRAP) {
+            Exceptions.raise(ConstraintDeclarationException::new, "Could not 
determine %s for %s",
+                ValueExtractor.class.getSimpleName(), containerClass);
         }
-        return candidateMap.values().iterator().next();
+        return Optional.empty();
+    }
+
+    private static ContainerElementKey translateTo(Class<?> containerClass, 
ContainerElementKey key) {
+        final Class<?> keyContainer = key.getContainerClass();
+        if (keyContainer.equals(containerClass)) {
+            return key;
+        }
+        Validate.validState(keyContainer.isAssignableFrom(containerClass), 
"Cannot render %s in terms of %s", key,
+            containerClass);
+        if (key.getTypeArgumentIndex() == null) {
+            return new ContainerElementKey(containerClass, null);
+        }
+        Integer typeArgumentIndex = null;
+        final Map<TypeVariable<?>, Type> typeArguments = 
TypeUtils.getTypeArguments(containerClass, keyContainer);
+        Type t = 
typeArguments.get(keyContainer.getTypeParameters()[key.getTypeArgumentIndex().intValue()]);
+        while (t instanceof TypeVariable<?>) {
+            final TypeVariable<?> var = (TypeVariable<?>) t;
+            if (containerClass.equals(var.getGenericDeclaration())) {
+                typeArgumentIndex = 
Integer.valueOf(ObjectUtils.indexOf(containerClass.getTypeParameters(), var));
+                break;
+            }
+            t = typeArguments.get(t);
+        }
+        return new ContainerElementKey(containerClass, typeArgumentIndex);
     }
 
     private void populate(Supplier<Map<ContainerElementKey, 
ValueExtractor<?>>> target) {
         Optional.ofNullable(parent).ifPresent(p -> p.populate(target));
         valueExtractors.optional().ifPresent(m -> target.get().putAll(m));
     }
+
+    private void clearCache() {
+        searchCache.optional().ifPresent(Map::clear);
+    }
 }

Reply via email to