You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by ra...@apache.org on 2018/10/12 15:00:51 UTC

svn commit: r1843674 [13/22] - in /tomee/deps/branches/bval-2: ./ bundle/ bundle/src/ bundle/src/main/ bundle/src/main/appended-resources/ bundle/src/main/appended-resources/META-INF/ bval-extras/ bval-extras/src/ bval-extras/src/main/ bval-extras/src/...

Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java
URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java?rev=1843674&view=auto
==============================================================================
--- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java (added)
+++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java Fri Oct 12 15:00:48 2018
@@ -0,0 +1,543 @@
+/*
+ * 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.util;
+
+import static java.util.Comparator.comparing;
+import static java.util.Comparator.naturalOrder;
+import static java.util.Comparator.nullsFirst;
+
+import java.io.Serializable;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import javax.validation.ElementKind;
+import javax.validation.Path;
+import javax.validation.Path.BeanNode;
+import javax.validation.Path.ConstructorNode;
+import javax.validation.Path.ContainerElementNode;
+import javax.validation.Path.MethodNode;
+import javax.validation.Path.Node;
+import javax.validation.Path.ParameterNode;
+import javax.validation.Path.PropertyNode;
+
+import org.apache.bval.util.Comparators;
+import org.apache.bval.util.Exceptions;
+
+public abstract class NodeImpl implements Path.Node, Serializable {
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 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));
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    private static final Comparator<Object> KEY_COMPARATOR = nullsFirst(((Comparator<Object>) (quid, quo) -> {
+        if (quid instanceof Comparable<?> && quo instanceof Comparable<?>) {
+            try {
+                return Comparator.<Comparable> naturalOrder().compare((Comparable) quid, (Comparable) quo);
+            } catch (Exception e) {
+                // apparently not mutually comparable
+            }
+        }
+        if (quid instanceof Class<?> && quo instanceof Class<?>) {
+            return CLASS_COMPARATOR.compare((Class<?>) quid, (Class<?>) quo);
+        }
+        return 0;
+    }).thenComparing((Function<Object, String>) Objects::toString));
+
+    private static final char INDEX_OPEN = '[';
+    private static final char INDEX_CLOSE = ']';
+
+    private static <T extends Path.Node> Optional<T> optional(Class<T> type, Object o) {
+        return Optional.ofNullable(o).filter(type::isInstance).map(type::cast);
+    }
+
+    /**
+     * Append a Node to the specified StringBuilder.
+     * 
+     * @param node
+     * @param to
+     * @return to
+     */
+    public static StringBuilder appendNode(Node node, StringBuilder to) {
+        if (node.isInIterable()) {
+            to.append(INDEX_OPEN);
+            if (node.getIndex() != null) {
+                to.append(node.getIndex());
+            } else if (node.getKey() != null) {
+                to.append(node.getKey());
+            }
+            to.append(INDEX_CLOSE);
+        }
+        if (node.getName() != null) {
+            if (to.length() > 0) {
+                to.append(PathImpl.PROPERTY_PATH_SEPARATOR);
+            }
+            to.append(node.getName());
+        }
+        return to;
+    }
+
+    /**
+     * Get a NodeImpl indexed from the preceding node (or root).
+     * 
+     * @param index
+     * @return NodeImpl
+     */
+    public static NodeImpl atIndex(Integer index) {
+        final NodeImpl result = new NodeImpl.PropertyNodeImpl((String) null);
+        result.setIndex(index);
+        return result;
+    }
+
+    /**
+     * Get a NodeImpl keyed from the preceding node (or root).
+     * 
+     * @param key
+     * @return NodeImpl
+     */
+    public static NodeImpl atKey(Object key) {
+        final NodeImpl result = new NodeImpl.PropertyNodeImpl((String) null);
+        result.setKey(key);
+        return result;
+    }
+
+    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 (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;
+        }
+        return quo.isInIterable() ? -1 : 0;
+    }
+
+    private static int compareSpecificNodeInfo(Node quid, Node quo) {
+        final ElementKind kind = quid.getKind();
+        final int k = kind.compareTo(quo.getKind());
+        if (k != 0) {
+            return k;
+        }
+        final Comparator<Node> cmp;
+        switch (kind) {
+        case BEAN:
+            cmp = comparing(to(BeanNode.class), comparing(BeanNode::getContainerClass, CLASS_COMPARATOR)
+                .thenComparing(BeanNode::getTypeArgumentIndex, nullsFirst(naturalOrder())));
+            break;
+        case PROPERTY:
+            cmp = comparing(to(PropertyNode.class), comparing(PropertyNode::getContainerClass, CLASS_COMPARATOR)
+                .thenComparing(PropertyNode::getTypeArgumentIndex, nullsFirst(naturalOrder())));
+            break;
+        case CONTAINER_ELEMENT:
+            cmp = comparing(to(ContainerElementNode.class),
+                comparing(ContainerElementNode::getContainerClass, CLASS_COMPARATOR)
+                    .thenComparing(ContainerElementNode::getTypeArgumentIndex, nullsFirst(naturalOrder())));
+            break;
+        case CONSTRUCTOR:
+            cmp = comparing(to(ConstructorNode.class).andThen(ConstructorNode::getParameterTypes),
+                Comparators.comparingIterables(CLASS_COMPARATOR));
+            break;
+        case METHOD:
+            cmp = comparing(to(MethodNode.class).andThen(MethodNode::getParameterTypes),
+                Comparators.comparingIterables(CLASS_COMPARATOR));
+            break;
+        case PARAMETER:
+            cmp = comparing(to(ParameterNode.class).andThen(ParameterNode::getParameterIndex));
+            break;
+        default:
+            return 0;
+        }
+        return cmp.compare(quid, quo);
+    }
+
+    private static <T> Function<Object, T> to(Class<T> type) {
+        return type::cast;
+    }
+
+    private String name;
+    private boolean inIterable;
+    private Integer index;
+    private int parameterIndex;
+    private Object key;
+    private List<Class<?>> parameterTypes;
+    private Class<?> containerType;
+    private Integer typeArgumentIndex;
+
+    /**
+     * Create a new NodeImpl instance.
+     * 
+     * @param name
+     */
+    private NodeImpl(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Create a new NodeImpl instance.
+     * 
+     * @param node
+     */
+    NodeImpl(Path.Node node) {
+        this(node.getName());
+        this.inIterable = node.isInIterable();
+        this.index = node.getIndex();
+        this.key = node.getKey();
+
+        if (node instanceof NodeImpl) {
+            final NodeImpl n = (NodeImpl) node;
+            this.parameterIndex = n.parameterIndex;
+            this.parameterTypes = n.parameterTypes;
+            this.containerType = n.containerType;
+            this.typeArgumentIndex = n.typeArgumentIndex;
+        }
+    }
+
+    <T extends Path.Node> NodeImpl(Path.Node node, Class<T> nodeType, Consumer<T> handler) {
+        this(node);
+        Optional.of(node).filter(nodeType::isInstance).map(nodeType::cast).ifPresent(handler);
+    }
+
+    private NodeImpl() {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * @param name
+     *            the name to set
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isInIterable() {
+        return inIterable;
+    }
+
+    /**
+     * Set whether this node represents a contained value of an {@link Iterable} or {@link Map}.
+     * 
+     * @param inIterable
+     */
+    public void setInIterable(boolean inIterable) {
+        this.inIterable = inIterable;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Integer getIndex() {
+        return index;
+    }
+
+    /**
+     * Set the index of this node, implying <code>inIterable</code>.
+     * 
+     * @param index
+     */
+    public void setIndex(Integer index) {
+        inIterable = true;
+        this.index = index;
+        this.key = null;
+    }
+
+    public void setParameterIndex(final Integer parameterIndex) {
+        this.parameterIndex = parameterIndex;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Object getKey() {
+        return key;
+    }
+
+    /**
+     * Set the map key of this node, implying <code>inIterable</code>.
+     * 
+     * @param key
+     */
+    public void setKey(Object key) {
+        inIterable = true;
+        this.key = key;
+        this.index = null;
+    }
+
+    @Override
+    public <T extends Node> T as(final Class<T> nodeType) {
+        Exceptions.raiseUnless(nodeType.isInstance(this), ClassCastException::new, "Type %s not supported by %s",
+            f -> f.args(nodeType, getClass()));
+        return nodeType.cast(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        return appendNode(this, new StringBuilder()).toString();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || !getClass().equals(o.getClass())) {
+            return false;
+        }
+        return NODE_EQUALITY_COMPARATOR.compare(this, (NodeImpl) o) == 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(name, Boolean.valueOf(inIterable), index, key, getKind());
+    }
+
+    public int getParameterIndex() {
+        return parameterIndex;
+    }
+
+    public List<Class<?>> getParameterTypes() {
+        return parameterTypes;
+    }
+
+    public void setParameterTypes(final List<Class<?>> parameterTypes) {
+        this.parameterTypes = parameterTypes;
+    }
+
+    public Class<?> getContainerClass() {
+        return containerType;
+    }
+
+    public Integer getTypeArgumentIndex() {
+        return typeArgumentIndex;
+    }
+
+    public NodeImpl inIterable() {
+        setInIterable(true);
+        return this;
+    }
+
+    public NodeImpl inContainer(Class<?> containerType, Integer typeArgumentIndex) {
+        this.containerType = containerType;
+        this.typeArgumentIndex = typeArgumentIndex;
+        return this;
+    }
+
+    @SuppressWarnings("serial")
+    public static class ParameterNodeImpl extends NodeImpl implements Path.ParameterNode {
+        public ParameterNodeImpl(final Node cast) {
+            super(cast);
+            optional(Path.ParameterNode.class, cast).ifPresent(n -> setParameterIndex(n.getParameterIndex()));
+        }
+
+        public ParameterNodeImpl(final String name, final int idx) {
+            super(name);
+            setParameterIndex(idx);
+        }
+
+        @Override
+        public ElementKind getKind() {
+            return ElementKind.PARAMETER;
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public static class ConstructorNodeImpl extends NodeImpl implements Path.ConstructorNode {
+        public ConstructorNodeImpl(final Node cast) {
+            super(cast);
+            optional(Path.ConstructorNode.class, cast).ifPresent(n -> setParameterTypes(n.getParameterTypes()));
+        }
+
+        public ConstructorNodeImpl(final String simpleName, List<Class<?>> paramTypes) {
+            super(simpleName);
+            setParameterTypes(paramTypes);
+        }
+
+        @Override
+        public ElementKind getKind() {
+            return ElementKind.CONSTRUCTOR;
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public static class CrossParameterNodeImpl extends NodeImpl implements Path.CrossParameterNode {
+        public CrossParameterNodeImpl() {
+            super("<cross-parameter>");
+        }
+
+        public CrossParameterNodeImpl(final Node cast) {
+            super(cast);
+        }
+
+        @Override
+        public ElementKind getKind() {
+            return ElementKind.CROSS_PARAMETER;
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public static class MethodNodeImpl extends NodeImpl implements Path.MethodNode {
+        public MethodNodeImpl(final Node cast) {
+            super(cast);
+            optional(Path.MethodNode.class, cast).ifPresent(n -> setParameterTypes(n.getParameterTypes()));
+        }
+
+        public MethodNodeImpl(final String name, final List<Class<?>> classes) {
+            super(name);
+            setParameterTypes(classes);
+        }
+
+        @Override
+        public ElementKind getKind() {
+            return ElementKind.METHOD;
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public static class ReturnValueNodeImpl extends NodeImpl implements Path.ReturnValueNode {
+        public ReturnValueNodeImpl(final Node cast) {
+            super(cast);
+        }
+
+        public ReturnValueNodeImpl() {
+            super("<return value>");
+        }
+
+        @Override
+        public ElementKind getKind() {
+            return ElementKind.RETURN_VALUE;
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public static class PropertyNodeImpl extends NodeImpl implements Path.PropertyNode {
+        public PropertyNodeImpl(final String name) {
+            super(name);
+        }
+
+        public PropertyNodeImpl(final Node cast) {
+            super(cast);
+            optional(Path.PropertyNode.class, cast)
+                .ifPresent(n -> inContainer(n.getContainerClass(), n.getTypeArgumentIndex()));
+        }
+
+        @Override
+        public ElementKind getKind() {
+            return ElementKind.PROPERTY;
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public static class BeanNodeImpl extends NodeImpl implements Path.BeanNode {
+        public BeanNodeImpl() {
+            // no-op
+        }
+
+        public BeanNodeImpl(final Node cast) {
+            super(cast);
+            optional(Path.BeanNode.class, cast)
+                .ifPresent(n -> inContainer(n.getContainerClass(), n.getTypeArgumentIndex()));
+        }
+
+        @Override
+        public ElementKind getKind() {
+            return ElementKind.BEAN;
+        }
+    }
+
+    @SuppressWarnings("serial")
+    public static class ContainerElementNodeImpl extends NodeImpl implements Path.ContainerElementNode {
+
+        public ContainerElementNodeImpl(String name) {
+            super(name);
+        }
+
+        public ContainerElementNodeImpl(String name, Class<?> containerType, Integer typeArgumentIndex) {
+            this(name);
+            inContainer(containerType, typeArgumentIndex);
+        }
+
+        public ContainerElementNodeImpl(final Node cast) {
+            super(cast);
+            optional(Path.ContainerElementNode.class, cast)
+                .ifPresent(n -> inContainer(n.getContainerClass(), n.getTypeArgumentIndex()));
+        }
+
+        @Override
+        public ElementKind getKind() {
+            return ElementKind.CONTAINER_ELEMENT;
+        }
+    }
+}

Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathImpl.java
URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathImpl.java?rev=1843674&view=auto
==============================================================================
--- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathImpl.java (added)
+++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathImpl.java Fri Oct 12 15:00:48 2018
@@ -0,0 +1,354 @@
+/*
+ * 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.util;
+
+import java.io.Serializable;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Objects;
+
+import javax.validation.Path;
+
+import org.apache.bval.util.Comparators;
+import org.apache.bval.util.Exceptions;
+
+/**
+ * Description: object holding the property path as a list of nodes. (Implementation partially based on reference
+ * implementation) <br/>
+ * This class is not synchronized.
+ * 
+ * @version $Rev: 1498347 $ $Date: 2013-07-01 12:06:18 +0200 (lun., 01 juil. 2013) $
+ */
+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 = ".";
+
+    /**
+     * Builds non-root paths from expressions.
+     */
+    public static class Builder implements PathNavigation.Callback<PathImpl> {
+        private final PathImpl result = PathImpl.create();
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void handleProperty(String name) {
+            result.addProperty(name);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void handleIndexOrKey(String value) {
+            // with no context to guide us, we can only parse ints and fall back to String keys:
+            NodeImpl node;
+            try {
+                node = NodeImpl.atIndex(Integer.parseInt(value));
+            } catch (NumberFormatException e) {
+                node = NodeImpl.atKey(value);
+            }
+            result.addNode(node);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public PathImpl result() {
+            return result;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void handleGenericInIterable() {
+            result.addNode(NodeImpl.atIndex(null));
+        }
+    }
+
+    /**
+     * Returns a {@code Path} instance representing the path described by the given string. To create a root node the
+     * empty string should be passed. Note: This signature is to maintain pluggability with the RI impl.
+     * 
+     * @param propertyPath
+     *            the path as string representation.
+     * @return a {@code Path} instance representing the path described by the given string.
+     */
+    public static PathImpl createPathFromString(String propertyPath) {
+        if (propertyPath == null || propertyPath.isEmpty()) {
+            return create();
+        }
+        return PathNavigation.navigateAndReturn(propertyPath, new Builder());
+    }
+
+    /**
+     * Create a {@link PathImpl} instance representing the specified path.
+     *
+     * @return PathImpl
+     */
+    public static PathImpl create() {
+        final PathImpl path = new PathImpl();
+        final NodeImpl node = new NodeImpl.BeanNodeImpl();
+        path.addNode(node);
+        return path;
+    }
+
+    /**
+     * Copy another Path.
+     * 
+     * @param path
+     * @return new {@link PathImpl}
+     */
+    public static PathImpl copy(Path path) {
+        return path == null ? null : new PathImpl(path);
+    }
+
+    public static PathImpl of(Path path) {
+        return path instanceof PathImpl ? (PathImpl) path : copy(path);
+    }
+
+    private static NodeImpl newNode(final Node cast) {
+        if (BeanNode.class.isInstance(cast)) {
+            return new NodeImpl.BeanNodeImpl(cast);
+        }
+        if (MethodNode.class.isInstance(cast)) {
+            return new NodeImpl.MethodNodeImpl(cast);
+        }
+        if (ConstructorNode.class.isInstance(cast)) {
+            return new NodeImpl.ConstructorNodeImpl(cast);
+        }
+        if (ReturnValueNode.class.isInstance(cast)) {
+            return new NodeImpl.ReturnValueNodeImpl(cast);
+        }
+        if (ParameterNode.class.isInstance(cast)) {
+            return new NodeImpl.ParameterNodeImpl(cast);
+        }
+        if (CrossParameterNode.class.isInstance(cast)) {
+            return new NodeImpl.CrossParameterNodeImpl(cast);
+        }
+        if (ContainerElementNode.class.isInstance(cast)) {
+            return new NodeImpl.ContainerElementNodeImpl(cast);
+        }
+        return new NodeImpl.PropertyNodeImpl(cast);
+    }
+
+    private static boolean isAwaitingPropertyName(NodeImpl n) {
+        return n != null && n.getName() == null && (n.isInIterable() || n.getContainerClass() != null);
+    }
+
+    private final LinkedList<NodeImpl> nodeList = new LinkedList<>();
+
+    private PathImpl() {
+    }
+
+    private PathImpl(Iterable<? extends Node> nodes) {
+        nodes.forEach(n -> nodeList.add(newNode(n)));
+    }
+
+    /**
+     * Learn whether this {@link PathImpl} points to the root of its graph.
+     * 
+     * @return true if no child nodes
+     */
+    // our implementation stores a nameless root node.
+    public boolean isRootPath() {
+        if (nodeList.size() != 1) {
+            return false;
+        }
+        final Path.Node first = nodeList.peekFirst();
+        return !first.isInIterable() && first.getName() == null;
+    }
+
+    /**
+     * Add a node to this {@link PathImpl}.
+     * 
+     * @param node
+     *            to add
+     * @return {@code this}, fluently
+     */
+    public PathImpl addNode(Node node) {
+        final NodeImpl impl = node instanceof NodeImpl ? (NodeImpl) node : newNode(node);
+        if (isRootPath()) {
+            nodeList.pop();
+        }
+        nodeList.add(impl);
+        return this;
+    }
+
+    /**
+     * Encapsulate the node manipulations needed to add a named property to this path.
+     * 
+     * @param name
+     * @return {@code this}, fluently
+     */
+    public PathImpl addProperty(String name) {
+        if (!nodeList.isEmpty()) {
+            NodeImpl leaf = getLeafNode();
+            if (isAwaitingPropertyName(leaf)) {
+                if (!PropertyNode.class.isInstance(leaf)) {
+                    final NodeImpl tmp = new NodeImpl.PropertyNodeImpl(leaf);
+                    removeLeafNode();
+                    addNode(tmp);
+                    leaf = tmp;
+                }
+                leaf.setName(name);
+                return this;
+            }
+        }
+        return addNode(new NodeImpl.PropertyNodeImpl(name));
+    }
+
+    public PathImpl addBean() {
+        final NodeImpl.BeanNodeImpl node;
+        if (!nodeList.isEmpty() && isAwaitingPropertyName(getLeafNode())) {
+            node = new NodeImpl.BeanNodeImpl(removeLeafNode());
+        } else {
+            node = new NodeImpl.BeanNodeImpl();
+        }
+        return addNode(node);
+    }
+
+    /**
+     * Trim the leaf node from this {@link PathImpl}.
+     * 
+     * @return the node removed
+     * @throws IllegalStateException
+     *             if no nodes are found
+     */
+    public NodeImpl removeLeafNode() {
+        Exceptions.raiseIf(isRootPath() || nodeList.isEmpty(), IllegalStateException::new, "No nodes in path!");
+
+        try {
+            return nodeList.removeLast();
+        } finally {
+            if (nodeList.isEmpty()) {
+                nodeList.add(new NodeImpl.BeanNodeImpl());
+            }
+        }
+    }
+
+    /**
+     * Get the leaf node (if any) from this {@link PathImpl}
+     * 
+     * @return {@link NodeImpl}
+     */
+    public NodeImpl getLeafNode() {
+        if (nodeList.isEmpty()) {
+            return null;
+        }
+        return nodeList.peekLast();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Iterator<Path.Node> iterator() {
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        final Iterator<Path.Node> result = ((List) nodeList).iterator();
+        return result;
+    }
+
+    /**
+     * Learn whether <code>path</code> is a parent to <code>this</code>.
+     * 
+     * @param path
+     * @return <code>true</code> if our nodes begin with nodes equal to those found in <code>path</code>
+     */
+    public boolean isSubPathOf(Path path) {
+        if (path instanceof PathImpl && ((PathImpl) path).isRootPath()) {
+            return true;
+        }
+        final Iterator<Node> pathIter = path.iterator();
+        final Iterator<Node> thisIter = iterator();
+        while (pathIter.hasNext()) {
+            final Node pathNode = pathIter.next();
+            if (!thisIter.hasNext()) {
+                return false;
+            }
+            final Node thisNode = thisIter.next();
+            if (pathNode.isInIterable()) {
+                if (!thisNode.isInIterable()) {
+                    return false;
+                }
+                if (pathNode.getIndex() != null && !pathNode.getIndex().equals(thisNode.getIndex())) {
+                    return false;
+                }
+                if (pathNode.getKey() != null && !pathNode.getKey().equals(thisNode.getKey())) {
+                    return false;
+                }
+            } else if (thisNode.isInIterable()) {
+                // in this case we have shown that the proposed parent is not
+                // indexed, and we are, thus the paths cannot match
+                return false;
+            }
+            if (pathNode.getName() == null || pathNode.getName().equals(thisNode.getName())) {
+                continue;
+            }
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        final StringBuilder builder = new StringBuilder();
+        for (Path.Node node : this) {
+            NodeImpl.appendNode(node, builder);
+        }
+        return builder.toString();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || !getClass().equals(o.getClass())) {
+            return false;
+        }
+        return Objects.equals(nodeList, ((PathImpl) o).nodeList);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(nodeList);
+    }
+}

Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathNavigation.java
URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathNavigation.java?rev=1843674&view=auto
==============================================================================
--- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathNavigation.java (added)
+++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathNavigation.java Fri Oct 12 15:00:48 2018
@@ -0,0 +1,374 @@
+/*
+ *  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.util;
+
+import static org.apache.bval.util.Escapes.unescapeJava;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.text.ParsePosition;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.validation.ValidationException;
+
+import org.apache.bval.util.Exceptions;
+import org.apache.bval.util.Validate;
+
+/**
+ * Defines a path navigation algorithm and a means of interacting with same.
+ * 
+ * @version $Rev: 1136233 $ $Date: 2011-06-15 17:49:27 -0500 (Wed, 15 Jun 2011) $
+ */
+public class PathNavigation {
+
+    /**
+     * Path traversal callback function interface.
+     */
+    public interface Callback<T> {
+        /**
+         * Handle a .-delimited property.
+         * 
+         * @param name
+         */
+        void handleProperty(String name);
+
+        /**
+         * Handle an index or key embedded in [].
+         * 
+         * @param value
+         */
+        void handleIndexOrKey(String value);
+
+        /**
+         * Handle contiguous [].
+         */
+        void handleGenericInIterable();
+
+        /**
+         * Return a result. Called after navigation is complete.
+         * 
+         * @return result
+         */
+        T result();
+    }
+
+    /**
+     * Callback "procedure" that always returns null.
+     */
+    public static abstract class CallbackProcedure implements Callback<Void> {
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public final Void result() {
+            complete();
+            return null;
+        }
+
+        /**
+         * Complete this CallbackProcedure. Default implementation is noop.
+         */
+        protected void complete() {
+        }
+    }
+
+    public static class CompositeCallbackProcedure extends CallbackProcedure {
+        private final List<Callback<?>> delegates;
+
+        public CompositeCallbackProcedure(Callback<?>... delegates) {
+            this(new ArrayList<>(Arrays.asList(delegates)));
+        }
+
+        public CompositeCallbackProcedure(List<Callback<?>> delegates) {
+            super();
+            this.delegates = Validate.notNull(delegates);
+        }
+
+        @Override
+        public void handleProperty(String name) {
+            delegates.forEach(d -> d.handleProperty(name));
+        }
+
+        @Override
+        public void handleIndexOrKey(String value) {
+            delegates.forEach(d -> d.handleIndexOrKey(value));
+        }
+
+        @Override
+        public void handleGenericInIterable() {
+            delegates.forEach(Callback::handleGenericInIterable);
+        }
+    }
+
+    private static class QuotedStringParser {
+        String parseQuotedString(CharSequence path, PathPosition pos) throws Exception {
+            final int len = path.length();
+            final int start = pos.getIndex();
+            if (start < len) {
+                final char quote = path.charAt(start);
+                pos.next();
+                final StringWriter w = new StringWriter();
+                while (pos.getIndex() < len) {
+                    final int here = pos.getIndex();
+                    // look for matching quote
+                    if (path.charAt(here) == quote) {
+                        pos.next();
+                        return w.toString();
+                    }
+                    handleNextChar(path, pos, w);
+                }
+                // if reached, reset due to no ending quote found
+                pos.setIndex(start);
+            }
+            return null;
+        }
+
+        protected void handleNextChar(CharSequence path, PathPosition pos, Writer target) throws IOException {
+            final int codePoints = unescapeJava(path, pos.getIndex(), target);
+            if (codePoints == 0) {
+                target.write(Character.toChars(Character.codePointAt(path, pos.getIndex())));
+                pos.next();
+            } else {
+                for (int i = 0; i < codePoints; i++) {
+                    pos.plus(Character.charCount(Character.codePointAt(path, pos.getIndex())));
+                }
+            }
+        }
+    }
+
+    private static final QuotedStringParser QUOTED_STRING_PARSER = new QuotedStringParser();
+
+    /**
+     * Create a new PathNavigation instance.
+     */
+    private PathNavigation() {
+    }
+
+    /**
+     * Navigate a path using the specified callback, returning its result.
+     * 
+     * @param <T>
+     * @param propertyPath
+     *            , null is assumed empty/root
+     * @param callback
+     * @return T result
+     */
+    public static <T> T navigateAndReturn(CharSequence propertyPath, Callback<? extends T> callback) {
+        try {
+            parse(propertyPath == null ? "" : propertyPath, new PathPosition(callback));
+        } catch (ValidationException | IllegalArgumentException ex) {
+            throw ex;
+        } catch (Exception e) {
+            Exceptions.raise(ValidationException::new, e, "invalid property: %s", propertyPath);
+        }
+        return callback.result();
+    }
+
+    /**
+     * Navigate a path using the specified callback.
+     * 
+     * @param propertyPath
+     * @param callback
+     */
+    public static void navigate(CharSequence propertyPath, Callback<?> callback) {
+        navigateAndReturn(propertyPath, callback);
+    }
+
+    private static void parse(CharSequence path, PathPosition pos) throws Exception {
+        int len = path.length();
+        boolean sep = true;
+        while (pos.getIndex() < len) {
+            int here = pos.getIndex();
+            char c = path.charAt(here);
+            switch (c) {
+            case ']':
+                Exceptions.raise(IllegalStateException::new, "Position %s: unexpected '%s'", here, c);
+            case '[':
+                handleIndex(path, pos.next());
+                break;
+            case '.':
+                Exceptions.raiseIf(sep, IllegalStateException::new,
+                    "Position %s: expected property, index/key, or end of expression", here);
+
+                sep = true;
+                pos.next();
+                // fall through:
+            default:
+                Exceptions.raiseUnless(sep, IllegalStateException::new,
+                    "Position %s: expected property path separator, index/key, or end of expression", here);
+
+                pos.handleProperty(parseProperty(path, pos));
+            }
+            sep = false;
+        }
+    }
+
+    private static String parseProperty(CharSequence path, PathPosition pos) throws Exception {
+        final int len = path.length();
+        final int start = pos.getIndex();
+        loop: while (pos.getIndex() < len) {
+            switch (path.charAt(pos.getIndex())) {
+            case '[':
+            case ']':
+            case '.':
+                break loop;
+            }
+            pos.next();
+        }
+        Exceptions.raiseIf(pos.getIndex() == start, IllegalStateException::new, "Position %s: expected property",
+            start);
+
+        return path.subSequence(start, pos.getIndex()).toString();
+    }
+
+    /**
+     * Handles an index/key. If the text contained between [] is surrounded by a pair of " or ', it will be treated as a
+     * string which may contain Java escape sequences.
+     * 
+     * @param path
+     * @param pos
+     * @throws Exception
+     */
+    private static void handleIndex(CharSequence path, PathPosition pos) throws Exception {
+        final int len = path.length();
+        final int start = pos.getIndex();
+        if (start < len) {
+            final char first = path.charAt(pos.getIndex());
+            if (first == '"' || first == '\'') {
+                final String s = QUOTED_STRING_PARSER.parseQuotedString(path, pos);
+                if (s != null && path.charAt(pos.getIndex()) == ']') {
+                    pos.handleIndexOrKey(s);
+                    pos.next();
+                    return;
+                }
+            }
+            // no quoted string; match ] greedily
+            while (pos.getIndex() < len) {
+                final int here = pos.getIndex();
+                try {
+                    if (path.charAt(here) == ']') {
+                        if (here == start) {
+                            pos.handleGenericInIterable();
+                        } else {
+                            pos.handleIndexOrKey(path.subSequence(start, here).toString());
+                        }
+                        return;
+                    }
+                } finally {
+                    pos.next();
+                }
+            }
+        }
+        Exceptions.raise(IllegalStateException::new, "Position %s: unparsable index", start);
+    }
+
+    /**
+     * ParsePosition/Callback
+     */
+    private static class PathPosition extends ParsePosition implements Callback<Void> {
+        final Callback<?> delegate;
+
+        /**
+         * Create a new {@link PathPosition} instance.
+         * 
+         * @param delegate
+         */
+        private PathPosition(Callback<?> delegate) {
+            super(0);
+            this.delegate = delegate;
+        }
+
+        /**
+         * Increment and return this.
+         * 
+         * @return this
+         */
+        public PathPosition next() {
+            return plus(1);
+        }
+
+        /**
+         * Increase position and return this.
+         * 
+         * @param addend
+         * @return this
+         */
+        public PathPosition plus(int addend) {
+            setIndex(getIndex() + addend);
+            return this;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void handleProperty(String name) {
+            delegate.handleProperty(name);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void handleIndexOrKey(String value) {
+            delegate.handleIndexOrKey(value);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void handleGenericInIterable() {
+            delegate.handleGenericInIterable();
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public Void result() {
+            return null;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        /*
+         * Override equals to make findbugs happy; would simply ignore but doesn't seem to be possible at the inner
+         * class level without attaching the filter to the containing class.
+         */
+        @Override
+        public boolean equals(Object obj) {
+            return super.equals(obj);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        /*
+         * Override hashCode to make findbugs happy in the presence of overridden #equals :P
+         */
+        @Override
+        public int hashCode() {
+            return super.hashCode();
+        }
+    }
+}

Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/util/Proxies.java
URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/util/Proxies.java?rev=1843674&view=auto
==============================================================================
--- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/util/Proxies.java (added)
+++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/util/Proxies.java Fri Oct 12 15:00:48 2018
@@ -0,0 +1,55 @@
+/*
+ * 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.util;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+public final class Proxies {
+    private static final Set<String> KNOWN_PROXY_CLASSNAMES;
+
+    static {
+        final Set<String> s = new HashSet<>();
+        s.add("org.jboss.weld.bean.proxy.ProxyObject");
+        KNOWN_PROXY_CLASSNAMES = Collections.unmodifiableSet(s);
+    }
+
+    // get rid of proxies which probably contains wrong annotation metamodel
+    public static <T> Class<?> classFor(final Class<?> clazz) { // TODO: do we want a SPI with impl for guice, owb, openejb, ...?
+        if (isProxyClass(clazz)) {
+            final Class<?> parent = clazz.getSuperclass();
+            if (parent != null) {
+                return classFor(clazz.getSuperclass());
+            }
+        }
+        return clazz;
+    }
+
+    public static boolean isProxyClass(Class<?> clazz) {
+        if (KNOWN_PROXY_CLASSNAMES.contains(clazz.getName())) {
+            return true;
+        }
+        return clazz.getSimpleName().contains("$$");// a lot of proxies use this convention to avoid conflicts with inner/anonymous classes
+    }
+
+    private Proxies() {
+        // no-op
+    }
+}

Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/util/ToUnmodifiable.java
URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/util/ToUnmodifiable.java?rev=1843674&view=auto
==============================================================================
--- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/util/ToUnmodifiable.java (added)
+++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/util/ToUnmodifiable.java Fri Oct 12 15:00:48 2018
@@ -0,0 +1,77 @@
+/*
+ *  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.util;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+
+/**
+ * Utility {@link Collector} definitions.
+ */
+public class ToUnmodifiable {
+
+    /**
+     * Collector to unmodifiable {@link Set} with custom backing implementation.
+     * 
+     * @param set
+     *            {@link Supplier}
+     * @return {@link Collector}
+     */
+    public static <T> Collector<T, ?, Set<T>> set(Supplier<Set<T>> set) {
+        return Collectors.collectingAndThen(Collectors.toCollection(set),
+            t -> t.isEmpty() ? Collections.emptySet() : Collections.unmodifiableSet(t));
+    }
+
+    /**
+     * Collector to unmodifiable {@link Set} (maintains insertion order).
+     * 
+     * @return {@link Collector}
+     */
+    public static <T> Collector<T, ?, Set<T>> set() {
+        return set(LinkedHashSet::new);
+    }
+
+    /**
+     * Collector to unmodifiable {@link List}.
+     * 
+     * @return {@link Collector}
+     */
+    public static <T> Collector<T, ?, List<T>> list() {
+        return Collectors.collectingAndThen(Collectors.toList(),
+            t -> t.isEmpty() ? Collections.emptyList() : Collections.unmodifiableList(t));
+    }
+
+    /**
+     * Collector to unmodifiable {@link Map}.
+     * 
+     * @param keyMapper
+     * @param valueMapper
+     * @return {@link Collector}
+     */
+    public static <T, K, U> Collector<T, ?, Map<K, U>> map(Function<? super T, ? extends K> keyMapper,
+        Function<? super T, ? extends U> valueMapper) {
+        return Collectors.collectingAndThen(Collectors.toMap(keyMapper, valueMapper),
+            t -> t.isEmpty() ? Collections.emptyMap() : Collections.unmodifiableMap(t));
+    }
+}

Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ExtractValues.java
URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ExtractValues.java?rev=1843674&view=auto
==============================================================================
--- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ExtractValues.java (added)
+++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ExtractValues.java Fri Oct 12 15:00:48 2018
@@ -0,0 +1,109 @@
+/*
+ * 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.Exceptions;
+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();
+            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) {
+            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);
+            } catch (ValidationException e) {
+                throw e;
+            } catch (Exception e) {
+                throw new ValidationException(e);
+            }
+            return receiver.result.optional().orElse(Collections.emptyList());
+        }
+        return Collections.singletonList(context);
+    }
+}

Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/FxExtractor.java
URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/FxExtractor.java?rev=1843674&view=auto
==============================================================================
--- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/FxExtractor.java (added)
+++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/FxExtractor.java Fri Oct 12 15:00:48 2018
@@ -0,0 +1,96 @@
+/*
+ * 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.Optional;
+import java.util.function.BooleanSupplier;
+
+import javax.validation.valueextraction.ExtractedValue;
+import javax.validation.valueextraction.UnwrapByDefault;
+import javax.validation.valueextraction.ValueExtractor;
+
+import org.apache.bval.util.reflection.Reflection;
+
+import javafx.beans.property.ReadOnlyListProperty;
+import javafx.beans.property.ReadOnlyMapProperty;
+import javafx.beans.property.ReadOnlySetProperty;
+import javafx.beans.value.ObservableValue;
+
+@SuppressWarnings("restriction")
+public abstract class FxExtractor {
+    public static class Activation implements BooleanSupplier {
+
+        @Override
+        public boolean getAsBoolean() {
+            try {
+                return Reflection.toClass("javafx.beans.Observable") != null;
+            } catch (ClassNotFoundException e) {
+                return false;
+            }
+        }
+    }
+
+    @UnwrapByDefault
+    public static class ForObservableValue implements ValueExtractor<ObservableValue<@ExtractedValue ?>> {
+
+        @Override
+        public void extractValues(ObservableValue<?> originalValue, ValueExtractor.ValueReceiver receiver) {
+            receiver.value(null, originalValue.getValue());
+        }
+    }
+
+    public static class ForListProperty implements ValueExtractor<ReadOnlyListProperty<@ExtractedValue ?>> {
+
+        @Override
+        public void extractValues(ReadOnlyListProperty<?> originalValue, ValueExtractor.ValueReceiver receiver) {
+            Optional.ofNullable(originalValue.getValue()).ifPresent(l -> {
+                for (int i = 0, sz = l.size(); i < sz; i++) {
+                    receiver.indexedValue("<list element>", i, l.get(i));
+                }
+            });
+        }
+    }
+
+    public static class ForSetProperty implements ValueExtractor<ReadOnlySetProperty<@ExtractedValue ?>> {
+
+        @Override
+        public void extractValues(ReadOnlySetProperty<?> originalValue, ValueExtractor.ValueReceiver receiver) {
+            Optional.ofNullable(originalValue.getValue())
+                .ifPresent(s -> s.forEach(e -> receiver.iterableValue("<iterable element>", e)));
+        }
+    }
+
+    public static class ForMapPropertyKey implements ValueExtractor<ReadOnlyMapProperty<@ExtractedValue ?, ?>> {
+
+        @Override
+        public void extractValues(ReadOnlyMapProperty<?, ?> originalValue, ValueExtractor.ValueReceiver receiver) {
+            Optional.ofNullable(originalValue.getValue())
+                .ifPresent(m -> m.keySet().forEach(k -> receiver.keyedValue("<map key>", k, k)));
+        }
+    }
+
+    public static class ForMapPropertyValue implements ValueExtractor<ReadOnlyMapProperty<?, @ExtractedValue ?>> {
+
+        @Override
+        public void extractValues(ReadOnlyMapProperty<?, ?> originalValue, ValueExtractor.ValueReceiver receiver) {
+            Optional.ofNullable(originalValue.getValue()).ifPresent(
+                m -> m.entrySet().forEach(e -> receiver.keyedValue("<map value>", e.getKey(), e.getValue())));
+        }
+    }
+}

Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/IterableElementExtractor.java
URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/IterableElementExtractor.java?rev=1843674&view=auto
==============================================================================
--- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/IterableElementExtractor.java (added)
+++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/IterableElementExtractor.java Fri Oct 12 15:00:48 2018
@@ -0,0 +1,30 @@
+/*
+ * 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 javax.validation.valueextraction.ExtractedValue;
+import javax.validation.valueextraction.ValueExtractor;
+
+public class IterableElementExtractor implements ValueExtractor<Iterable<@ExtractedValue ?>> {
+
+    @Override
+    public void extractValues(Iterable<?> originalValue, ValueExtractor.ValueReceiver receiver) {
+        originalValue.forEach(v -> receiver.iterableValue("<iterable element>", v));
+    }
+}

Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ListElementExtractor.java
URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ListElementExtractor.java?rev=1843674&view=auto
==============================================================================
--- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ListElementExtractor.java (added)
+++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ListElementExtractor.java Fri Oct 12 15:00:48 2018
@@ -0,0 +1,34 @@
+/*
+ * 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.List;
+
+import javax.validation.valueextraction.ExtractedValue;
+import javax.validation.valueextraction.ValueExtractor;
+
+public class ListElementExtractor implements ValueExtractor<List<@ExtractedValue ?>> {
+
+    @Override
+    public void extractValues(List<?> originalValue, ValueExtractor.ValueReceiver receiver) {
+        for (int i = 0, sz = originalValue.size(); i < sz; i++) {
+            receiver.indexedValue("<list element>", i, originalValue.get(i));
+        }
+    }
+}

Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/MapExtractor.java
URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/MapExtractor.java?rev=1843674&view=auto
==============================================================================
--- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/MapExtractor.java (added)
+++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/MapExtractor.java Fri Oct 12 15:00:48 2018
@@ -0,0 +1,42 @@
+/*
+ * 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.Map;
+
+import javax.validation.valueextraction.ExtractedValue;
+import javax.validation.valueextraction.ValueExtractor;
+
+public abstract class MapExtractor {
+    public static class ForKey implements ValueExtractor<Map<@ExtractedValue ?, ?>> {
+
+        @Override
+        public void extractValues(Map<?, ?> originalValue, ValueExtractor.ValueReceiver receiver) {
+            originalValue.keySet().forEach(k -> receiver.keyedValue("<map key>", k, k));
+        }
+    }
+
+    public static class ForValue implements ValueExtractor<Map<?, @ExtractedValue ?>> {
+
+        @Override
+        public void extractValues(Map<?, ?> originalValue, ValueExtractor.ValueReceiver receiver) {
+            originalValue.entrySet().forEach(e -> receiver.keyedValue("<map value>", e.getKey(), e.getValue()));
+        }
+    }
+}

Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/OptionalExtractor.java
URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/OptionalExtractor.java?rev=1843674&view=auto
==============================================================================
--- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/OptionalExtractor.java (added)
+++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/OptionalExtractor.java Fri Oct 12 15:00:48 2018
@@ -0,0 +1,65 @@
+/*
+ * 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.Optional;
+import java.util.OptionalDouble;
+import java.util.OptionalInt;
+import java.util.OptionalLong;
+
+import javax.validation.valueextraction.ExtractedValue;
+import javax.validation.valueextraction.UnwrapByDefault;
+import javax.validation.valueextraction.ValueExtractor;
+
+public abstract class OptionalExtractor {
+    public static class ForObject implements ValueExtractor<Optional<@ExtractedValue ?>> {
+
+        @Override
+        public void extractValues(Optional<?> originalValue, ValueExtractor.ValueReceiver receiver) {
+            receiver.value(null, originalValue.orElse(null));
+        }
+    }
+
+    @UnwrapByDefault
+    public static class ForInt implements ValueExtractor<@ExtractedValue(type = Integer.class) OptionalInt> {
+
+        @Override
+        public void extractValues(OptionalInt originalValue, ValueExtractor.ValueReceiver receiver) {
+            receiver.value(null, originalValue.isPresent() ? Integer.valueOf(originalValue.getAsInt()) : null);
+        }
+    }
+
+    @UnwrapByDefault
+    public static class ForLong implements ValueExtractor<@ExtractedValue(type = Long.class) OptionalLong> {
+
+        @Override
+        public void extractValues(OptionalLong originalValue, ValueExtractor.ValueReceiver receiver) {
+            receiver.value(null, originalValue.isPresent() ? Long.valueOf(originalValue.getAsLong()) : null);
+        }
+    }
+
+    @UnwrapByDefault
+    public static class ForDouble implements ValueExtractor<@ExtractedValue(type = Double.class) OptionalDouble> {
+
+        @Override
+        public void extractValues(OptionalDouble originalValue, ValueExtractor.ValueReceiver receiver) {
+            receiver.value(null, originalValue.isPresent() ? Double.valueOf(originalValue.getAsDouble()) : null);
+        }
+    }
+}

Added: tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java
URL: http://svn.apache.org/viewvc/tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java?rev=1843674&view=auto
==============================================================================
--- tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java (added)
+++ tomee/deps/branches/bval-2/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java Fri Oct 12 15:00:48 2018
@@ -0,0 +1,341 @@
+/*
+ *  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.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;
+import java.util.Properties;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.BooleanSupplier;
+import java.util.function.Consumer;
+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.metadata.ValidateUnwrappedValue;
+import javax.validation.valueextraction.UnwrapByDefault;
+import javax.validation.valueextraction.ValueExtractor;
+import javax.validation.valueextraction.ValueExtractorDeclarationException;
+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;
+import org.apache.bval.util.reflection.Reflection.Interfaces;
+import org.apache.bval.util.reflection.TypeUtils;
+
+/**
+ * {@link ValueExtractor} collection of some level of a bean validation hierarchy.
+ */
+public class ValueExtractors {
+    public enum OnDuplicateContainerElementKey {
+        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;
+        }
+        
+        UnwrappingInfo inTermsOf(Class<?> containerClass) {
+            final Class<?> keyContainer = containerElementKey.getContainerClass();
+            if (keyContainer.equals(containerClass)) {
+                return this;
+            }
+            Validate.validState(keyContainer.isAssignableFrom(containerClass), "Cannot render %s in terms of %s",
+                containerElementKey, containerClass);
+
+            final ContainerElementKey key;
+
+            if (containerElementKey.getTypeArgumentIndex() == null) {
+                key = new ContainerElementKey(containerClass, null);
+            } else {
+                Integer typeArgumentIndex = null;
+                final Map<TypeVariable<?>, Type> typeArguments =
+                    TypeUtils.getTypeArguments(containerClass, keyContainer);
+                Type t = typeArguments
+                    .get(keyContainer.getTypeParameters()[containerElementKey.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);
+                }
+                key = new ContainerElementKey(containerClass, typeArgumentIndex);
+            }
+            return new UnwrappingInfo(key, valueExtractor);
+        }
+
+        @Override
+        public String toString() {
+            return String.format("%s:%s", containerElementKey, valueExtractor);
+        }
+    }
+
+    public static final ValueExtractors EMPTY =
+        new ValueExtractors(null, OnDuplicateContainerElementKey.EXCEPTION, Collections.emptyMap());
+
+    public static final ValueExtractors DEFAULT;
+    static {
+        final Properties defaultExtractors = new Properties();
+        try {
+            defaultExtractors.load(ValueExtractors.class.getResourceAsStream("DefaultExtractors.properties"));
+        } catch (IOException e) {
+            throw new IllegalStateException(e);
+        }
+        final Map<ContainerElementKey, ValueExtractor<?>> m = new TreeMap<>();
+        final Consumer<ValueExtractor<?>> put = ve -> m.put(ContainerElementKey.forValueExtractor(ve), ve);
+
+        split(defaultExtractors.getProperty(ValueExtractor.class.getName())).map(cn -> {
+            try {
+                @SuppressWarnings("unchecked")
+                final Class<? extends ValueExtractor<?>> result =
+                    (Class<? extends ValueExtractor<?>>) Reflection.toClass(cn).asSubclass(ValueExtractor.class);
+                return result;
+            } catch (Exception e) {
+                throw new IllegalStateException(e);
+            }
+        }).map(ValueExtractors::newInstance).forEach(put);
+
+        split(defaultExtractors.getProperty(ValueExtractor.class.getName() + ".container"))
+            .flatMap(ValueExtractors::loadValueExtractors).forEach(put);
+
+        DEFAULT = new ValueExtractors(null, OnDuplicateContainerElementKey.EXCEPTION, Collections.unmodifiableMap(m));
+    }
+
+    public static Class<?> getExtractedType(ValueExtractor<?> extractor, Type target) {
+        final ContainerElementKey key = ContainerElementKey.forValueExtractor(extractor);
+        Type result = key.getAnnotatedType().getType();
+        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", f -> f.args(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, ','));
+    }
+
+    private static <T> T newInstance(Class<T> t) {
+        try {
+            return t.getConstructor().newInstance();
+        } catch (NoSuchMethodException | InstantiationException | IllegalAccessException e) {
+            throw new IllegalStateException(e);
+        } catch (InvocationTargetException e) {
+            throw new IllegalStateException(e.getTargetException());
+        }
+    }
+
+    private static Stream<ValueExtractor<?>> loadValueExtractors(String containerClassName) {
+        try {
+            final Class<? extends BooleanSupplier> activation =
+                Reflection.toClass(containerClassName + "$Activation").asSubclass(BooleanSupplier.class);
+            if (!newInstance(activation).getAsBoolean()) {
+                return Stream.empty();
+            }
+        } catch (ClassNotFoundException e) {
+            // always active
+        }
+        final Class<?> containerClass;
+        try {
+            containerClass = Reflection.toClass(containerClassName);
+        } catch (ClassNotFoundException e) {
+            throw new IllegalStateException(e);
+        }
+        return Stream.of(containerClass.getClasses()).filter(ValueExtractor.class::isAssignableFrom).map(c -> {
+            @SuppressWarnings("unchecked")
+            final Class<? extends ValueExtractor<?>> result =
+                (Class<? extends ValueExtractor<?>>) c.asSubclass(ValueExtractor.class);
+            return result;
+        }).map(ValueExtractors::newInstance);
+    }
+
+    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() {
+        this(OnDuplicateContainerElementKey.EXCEPTION);
+    }
+
+    public ValueExtractors(OnDuplicateContainerElementKey onDuplicateContainerElementKey) {
+        this(DEFAULT, Validate.notNull(onDuplicateContainerElementKey));
+    }
+
+    private ValueExtractors(ValueExtractors parent, OnDuplicateContainerElementKey onDuplicateContainerElementKey) {
+        this.parent = parent;
+        this.onDuplicateContainerElementKey = onDuplicateContainerElementKey;
+    }
+
+    private ValueExtractors(ValueExtractors parent, OnDuplicateContainerElementKey onDuplicateContainerElementKey,
+        Map<ContainerElementKey, ValueExtractor<?>> backingMap) {
+        this(parent, onDuplicateContainerElementKey);
+        this.valueExtractors.reset(backingMap);
+    }
+
+    public ValueExtractors createChild() {
+        return createChild(OnDuplicateContainerElementKey.EXCEPTION);
+    }
+
+    public ValueExtractors createChild(OnDuplicateContainerElementKey onDuplicateContainerElementKey) {
+        final ValueExtractors child = new ValueExtractors(this, onDuplicateContainerElementKey);
+        children.get().add(child);
+        return child;
+    }
+
+    public void add(ValueExtractor<?> extractor) {
+        Validate.notNull(extractor);
+        final ContainerElementKey key = ContainerElementKey.forValueExtractor(extractor);
+        if (key == null) {
+            Exceptions.raise(IllegalStateException::new, "Computed null %s for %s",
+                ContainerElementKey.class.getSimpleName(), extractor);
+        }
+        final Map<ContainerElementKey, ValueExtractor<?>> m = valueExtractors.get();
+        if (onDuplicateContainerElementKey == OnDuplicateContainerElementKey.EXCEPTION) {
+            synchronized (this) {
+                if (m.containsKey(key)) {
+                    Exceptions.raise(ValueExtractorDeclarationException::new,
+                        "Multiple context-level %ss specified for %s", ValueExtractor.class.getSimpleName(), key);
+                }
+                m.put(key, extractor);
+            }
+        } else {
+            m.put(key, extractor);
+        }
+        children.optional().ifPresent(s -> s.stream().forEach(ValueExtractors::clearCache));
+    }
+
+    public Map<ContainerElementKey, ValueExtractor<?>> getValueExtractors() {
+        final Lazy<Map<ContainerElementKey, ValueExtractor<?>>> result = new Lazy<>(HashMap::new);
+        populate(result);
+        return result.optional().orElseGet(Collections::emptyMap);
+    }
+
+    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);
+        }
+        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();
+        }
+        throw Exceptions.create(ConstraintDeclarationException::new, "Could not determine %s for %s",
+            ValueExtractor.class.getSimpleName(), key);
+    }
+
+    public Optional<UnwrappingInfo> findUnwrappingInfo(Class<?> containerClass,
+        ValidateUnwrappedValue valueUnwrapping) {
+        if (valueUnwrapping == ValidateUnwrappedValue.SKIP) {
+            return Optional.empty();
+        }
+        final Map<ContainerElementKey, ValueExtractor<?>> allValueExtractors = getValueExtractors();
+
+        final Set<UnwrappingInfo> unwrapping = allValueExtractors.entrySet().stream()
+            .filter(e -> e.getKey().getContainerClass().isAssignableFrom(containerClass))
+            .filter(e -> valueUnwrapping == ValidateUnwrappedValue.UNWRAP || isUnwrapByDefault(e.getValue()))
+            .map(e -> new UnwrappingInfo(e.getKey(), e.getValue())).collect(Collectors.toSet());
+
+        final Optional<UnwrappingInfo> result =
+            maximallySpecific(unwrapping, u -> u.containerElementKey.getContainerClass())
+                .map(u -> u.inTermsOf(containerClass));
+
+        if (!result.isPresent() && valueUnwrapping == ValidateUnwrappedValue.UNWRAP) {
+            Exceptions.raise(ConstraintDeclarationException::new, "Could not determine %s for %s",
+                ValueExtractor.class.getSimpleName(), containerClass);
+        }
+        return result;
+    }
+
+    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);
+    }
+}