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); + } +}