http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeContextBuilderImpl.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeContextBuilderImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeContextBuilderImpl.java index 80e5b8f..f32db9f 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeContextBuilderImpl.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeContextBuilderImpl.java @@ -18,18 +18,19 @@ */ package org.apache.bval.jsr.util; -import org.apache.bval.jsr.ConstraintValidatorContextImpl; +import org.apache.bval.jsr.job.ConstraintValidatorContextImpl; import javax.validation.ConstraintValidatorContext; +import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderCustomizableContext; import javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.NodeContextBuilder; /** * Description: Implementation of {@link NodeContextBuilder}.<br/> */ -final class NodeContextBuilderImpl implements ConstraintValidatorContext.ConstraintViolationBuilder.NodeContextBuilder { - private final ConstraintValidatorContextImpl parent; - private final String messageTemplate; - private final PathImpl propertyPath; +public final class NodeContextBuilderImpl implements ConstraintValidatorContext.ConstraintViolationBuilder.NodeContextBuilder { + private final ConstraintValidatorContextImpl<?> context; + private final String template; + private final PathImpl path; // The name of the last "added" node, it will only be added if it has a non-null name // The actual incorporation in the path will take place when the definition of the current leaf node is complete private final NodeImpl node; @@ -40,10 +41,10 @@ final class NodeContextBuilderImpl implements ConstraintValidatorContext.Constra * @param template * @param path */ - NodeContextBuilderImpl(ConstraintValidatorContextImpl contextImpl, String template, PathImpl path, NodeImpl node) { - parent = contextImpl; - messageTemplate = template; - propertyPath = path; + NodeContextBuilderImpl(ConstraintValidatorContextImpl<?> contextImpl, String template, PathImpl path, NodeImpl node) { + this.context = contextImpl; + this.template = template; + this.path = path; this.node = node; } @@ -53,8 +54,8 @@ final class NodeContextBuilderImpl implements ConstraintValidatorContext.Constra @Override public ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderDefinedContext atKey(Object key) { node.setKey(key); - propertyPath.addNode(node); - return new NodeBuilderDefinedContextImpl(parent, messageTemplate, propertyPath); + path.addNode(node); + return new NodeBuilderDefinedContextImpl(context, template, path); } /** @@ -63,8 +64,8 @@ final class NodeContextBuilderImpl implements ConstraintValidatorContext.Constra @Override public ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderDefinedContext atIndex(Integer index) { node.setIndex(index); - propertyPath.addNode(node); - return new NodeBuilderDefinedContextImpl(parent, messageTemplate, propertyPath); + path.addNode(node); + return new NodeBuilderDefinedContextImpl(context, template, path); } /** @@ -78,14 +79,14 @@ final class NodeContextBuilderImpl implements ConstraintValidatorContext.Constra @Override public ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext addPropertyNode( String name) { - propertyPath.addNode(node); - return new NodeBuilderCustomizableContextImpl(parent, messageTemplate, propertyPath, name); + path.addNode(node); + return new NodeBuilderCustomizableContextImpl(context, template, path, name); } @Override public ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderCustomizableContext addBeanNode() { - propertyPath.addNode(node); - return new LeafNodeBuilderCustomizableContextImpl(parent, messageTemplate, propertyPath); + path.addNode(node); + return new LeafNodeBuilderCustomizableContextImpl(context, template, path); } /** @@ -93,9 +94,18 @@ final class NodeContextBuilderImpl implements ConstraintValidatorContext.Constra */ @Override public ConstraintValidatorContext addConstraintViolation() { - propertyPath.addNode(node); - parent.addError(messageTemplate, propertyPath); - return parent; + path.addNode(node); + context.addError(template, path); + return context; } -} \ No newline at end of file + @Override + public ContainerElementNodeBuilderCustomizableContext addContainerElementNode(String name, Class<?> containerType, + Integer typeArgumentIndex) { + final NodeImpl node = new NodeImpl.ContainerElementNodeImpl(name, containerType, typeArgumentIndex); + path.addNode(node); + return new ContainerElementNodeBuilderCustomizableContextImpl(context, template, path, name, containerType, + typeArgumentIndex); + } + +}
http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java index 5a08f0e..96f9421 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java @@ -21,16 +21,25 @@ package org.apache.bval.jsr.util; import javax.validation.ElementKind; import javax.validation.Path; import javax.validation.Path.Node; + +import org.apache.bval.util.Exceptions; + import java.io.Serializable; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; public class NodeImpl implements Path.Node, Serializable { private static final long serialVersionUID = 1L; private static final String INDEX_OPEN = "["; private static final String INDEX_CLOSE = "]"; - private List<Class<?>> parameterTypes; + + 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. @@ -63,7 +72,7 @@ public class NodeImpl implements Path.Node, Serializable { * @return NodeImpl */ public static NodeImpl atIndex(Integer index) { - NodeImpl result = new NodeImpl(); + final NodeImpl result = new NodeImpl(); result.setIndex(index); return result; } @@ -74,7 +83,7 @@ public class NodeImpl implements Path.Node, Serializable { * @return NodeImpl */ public static NodeImpl atKey(Object key) { - NodeImpl result = new NodeImpl(); + final NodeImpl result = new NodeImpl(); result.setKey(key); return result; } @@ -85,6 +94,9 @@ public class NodeImpl implements Path.Node, Serializable { private int parameterIndex; private Object key; private ElementKind kind; + private List<Class<?>> parameterTypes; + private Class<?> containerType; + private Integer typeArgumentIndex; /** * Create a new NodeImpl instance. @@ -99,13 +111,18 @@ public class NodeImpl implements Path.Node, Serializable { * @param node */ NodeImpl(Path.Node node) { - this.name = node.getName(); + this(node.getName()); this.inIterable = node.isInIterable(); this.index = node.getIndex(); this.key = node.getKey(); this.kind = node.getKind(); } + <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() { } @@ -191,10 +208,8 @@ public class NodeImpl implements Path.Node, Serializable { @Override public <T extends Node> T as(final Class<T> nodeType) { - if (nodeType.isInstance(this)) { - return nodeType.cast(this); - } - throw new ClassCastException("Type " + nodeType + " not supported"); + Exceptions.raiseUnless(nodeType.isInstance(this), ClassCastException::new, "Type %s not supported", nodeType); + return nodeType.cast(this); } /** @@ -213,29 +228,13 @@ public class NodeImpl implements Path.Node, Serializable { if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { - return false; - } - - NodeImpl node = (NodeImpl) o; - - if (inIterable != node.inIterable) { - return false; - } - if (index != null ? !index.equals(node.index) : node.index != null) { - return false; - } - if (key != null ? !key.equals(node.key) : node.key != null) { - return false; - } - if (name != null ? !name.equals(node.name) : node.name != null) { - return false; - } - if (kind != null ? !kind.equals(node.kind) : node.kind != null) { + if (o == null || !getClass().equals(o.getClass())) { return false; } + final NodeImpl node = (NodeImpl) o; - return true; + return inIterable == node.inIterable && Objects.equals(index, node.index) && Objects.equals(key, node.key) + && Objects.equals(name, node.name) && kind == node.kind; } /** @@ -243,12 +242,7 @@ public class NodeImpl implements Path.Node, Serializable { */ @Override public int hashCode() { - int result = name != null ? name.hashCode() : 0; - result = 31 * result + (inIterable ? 1 : 0); - result = 31 * result + (index != null ? index.hashCode() : 0); - result = 31 * result + (key != null ? key.hashCode() : 0); - result = 31 * result + (kind != null ? kind.hashCode() : 0); - return result; + return Objects.hash(name, Boolean.valueOf(inIterable), index, key, kind); } public int getParameterIndex() { @@ -263,12 +257,24 @@ public class NodeImpl implements Path.Node, Serializable { this.parameterTypes = parameterTypes; } + public Class<?> getContainerClass() { + return containerType; + } + + public Integer getTypeArgumentIndex() { + return typeArgumentIndex; + } + + public void inContainer(Class<?> containerType, Integer typeArgumentIndex) { + this.containerType = containerType; + this.typeArgumentIndex = typeArgumentIndex; + } + + @SuppressWarnings("serial") public static class ParameterNodeImpl extends NodeImpl implements Path.ParameterNode { public ParameterNodeImpl(final Node cast) { super(cast); - if (ParameterNodeImpl.class.isInstance(cast)) { - setParameterIndex(ParameterNodeImpl.class.cast(cast).getParameterIndex()); - } + optional(Path.ParameterNode.class, cast).ifPresent(n -> setParameterIndex(n.getParameterIndex())); } public ParameterNodeImpl(final String name, final int idx) { @@ -282,12 +288,11 @@ public class NodeImpl implements Path.Node, Serializable { } } + @SuppressWarnings("serial") public static class ConstructorNodeImpl extends NodeImpl implements Path.ConstructorNode { public ConstructorNodeImpl(final Node cast) { super(cast); - if (NodeImpl.class.isInstance(cast)) { - setParameterTypes(NodeImpl.class.cast(cast).parameterTypes); - } + optional(Path.ConstructorNode.class, cast).ifPresent(n -> setParameterTypes(n.getParameterTypes())); } public ConstructorNodeImpl(final String simpleName, List<Class<?>> paramTypes) { @@ -301,6 +306,7 @@ public class NodeImpl implements Path.Node, Serializable { } } + @SuppressWarnings("serial") public static class CrossParameterNodeImpl extends NodeImpl implements Path.CrossParameterNode { public CrossParameterNodeImpl() { super("<cross-parameter>"); @@ -316,12 +322,11 @@ public class NodeImpl implements Path.Node, Serializable { } } + @SuppressWarnings("serial") public static class MethodNodeImpl extends NodeImpl implements Path.MethodNode { public MethodNodeImpl(final Node cast) { super(cast); - if (MethodNodeImpl.class.isInstance(cast)) { - setParameterTypes(MethodNodeImpl.class.cast(cast).getParameterTypes()); - } + optional(Path.MethodNode.class, cast).ifPresent(n -> setParameterTypes(n.getParameterTypes())); } public MethodNodeImpl(final String name, final List<Class<?>> classes) { @@ -335,6 +340,7 @@ public class NodeImpl implements Path.Node, Serializable { } } + @SuppressWarnings("serial") public static class ReturnValueNodeImpl extends NodeImpl implements Path.ReturnValueNode { public ReturnValueNodeImpl(final Node cast) { super(cast); @@ -350,6 +356,7 @@ public class NodeImpl implements Path.Node, Serializable { } } + @SuppressWarnings("serial") public static class PropertyNodeImpl extends NodeImpl implements Path.PropertyNode { public PropertyNodeImpl(final String name) { super(name); @@ -357,6 +364,8 @@ public class NodeImpl implements Path.Node, Serializable { public PropertyNodeImpl(final Node cast) { super(cast); + optional(Path.PropertyNode.class, cast) + .ifPresent(n -> inContainer(n.getContainerClass(), n.getTypeArgumentIndex())); } @Override @@ -365,6 +374,7 @@ public class NodeImpl implements Path.Node, Serializable { } } + @SuppressWarnings("serial") public static class BeanNodeImpl extends NodeImpl implements Path.BeanNode { public BeanNodeImpl() { // no-op @@ -372,6 +382,8 @@ public class NodeImpl implements Path.Node, Serializable { public BeanNodeImpl(final Node cast) { super(cast); + optional(Path.BeanNode.class, cast) + .ifPresent(n -> inContainer(n.getContainerClass(), n.getTypeArgumentIndex())); } @Override @@ -379,4 +391,24 @@ public class NodeImpl implements Path.Node, Serializable { return ElementKind.BEAN; } } + + @SuppressWarnings("serial") + public static class ContainerElementNodeImpl extends NodeImpl implements Path.ContainerElementNode { + + public ContainerElementNodeImpl(String name, Class<?> containerType, Integer typeArgumentIndex) { + super(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; + } + } } http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathImpl.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathImpl.java index 59fba83..430d257 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathImpl.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathImpl.java @@ -18,16 +18,19 @@ */ package org.apache.bval.jsr.util; -import javax.validation.Path; import java.io.Serializable; -import java.util.ArrayList; 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.Exceptions; /** - * Description: object holding the property path as a list of nodes. - * (Implementation partially based on reference implementation) - * <br/> + * 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) $ @@ -41,8 +44,8 @@ public class PathImpl implements Path, Serializable { /** * Builds non-root paths from expressions. */ - private static class PathImplBuilder implements PathNavigation.Callback<PathImpl> { - PathImpl result = new PathImpl(); + public static class Builder implements PathNavigation.Callback<PathImpl> { + private final PathImpl result = PathImpl.create(); /** * {@inheritDoc} @@ -72,9 +75,6 @@ public class PathImpl implements Path, Serializable { */ @Override public PathImpl result() { - if (result.nodeList.isEmpty()) { - throw new IllegalStateException(); - } return result; } @@ -85,11 +85,8 @@ public class PathImpl implements Path, Serializable { public void handleGenericInIterable() { result.addNode(NodeImpl.atIndex(null)); } - } - private final List<NodeImpl> nodeList; - /** * 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. @@ -102,7 +99,7 @@ public class PathImpl implements Path, Serializable { if (propertyPath == null || propertyPath.isEmpty()) { return create(); } - return PathNavigation.navigateAndReturn(propertyPath, new PathImplBuilder()); + return PathNavigation.navigateAndReturn(propertyPath, new Builder()); } /** @@ -127,6 +124,10 @@ public class PathImpl implements Path, Serializable { 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 (PropertyNode.class.isInstance(cast)) { return new NodeImpl.PropertyNodeImpl(cast); @@ -140,9 +141,6 @@ public class PathImpl implements Path, Serializable { if (ConstructorNode.class.isInstance(cast)) { return new NodeImpl.ConstructorNodeImpl(cast); } - if (ConstructorNode.class.isInstance(cast)) { - return new NodeImpl.ConstructorNodeImpl(cast); - } if (ReturnValueNode.class.isInstance(cast)) { return new NodeImpl.ReturnValueNodeImpl(cast); } @@ -152,18 +150,19 @@ public class PathImpl implements Path, Serializable { if (CrossParameterNode.class.isInstance(cast)) { return new NodeImpl.CrossParameterNodeImpl(cast); } + if (ContainerElementNode.class.isInstance(cast)) { + return new NodeImpl.ContainerElementNodeImpl(cast); + } return new NodeImpl(cast); } + private final LinkedList<NodeImpl> nodeList = new LinkedList<>(); + private PathImpl() { - nodeList = new ArrayList<NodeImpl>(); } - private PathImpl(Iterable<Node> path) { - this(); - for (final Node node : path) { - nodeList.add(newNode(node)); - } + private PathImpl(Iterable<? extends Node> nodes) { + nodes.forEach(n -> nodeList.add(newNode(n))); } /** @@ -176,7 +175,7 @@ public class PathImpl implements Path, Serializable { if (nodeList.size() != 1) { return false; } - Path.Node first = nodeList.get(0); + final Path.Node first = nodeList.peekFirst(); return !first.isInIterable() && first.getName() == null; } @@ -186,13 +185,10 @@ public class PathImpl implements Path, Serializable { * @return PathImpl */ public PathImpl getPathWithoutLeafNode() { - List<Node> nodes = new ArrayList<Node>(nodeList); - PathImpl path = null; - if (nodes.size() > 1) { - nodes.remove(nodes.size() - 1); - path = new PathImpl(nodes); + if (nodeList.size() < 2) { + return null; } - return path; + return new PathImpl(nodeList.subList(0, nodeList.size() - 1)); } /** @@ -202,12 +198,11 @@ public class PathImpl implements Path, Serializable { * to add */ public void addNode(Node node) { - NodeImpl impl = node instanceof NodeImpl ? (NodeImpl) node : newNode(node); + final NodeImpl impl = node instanceof NodeImpl ? (NodeImpl) node : newNode(node); if (isRootPath()) { - nodeList.set(0, impl); - } else { - nodeList.add(impl); + nodeList.pop(); } + nodeList.add(impl); } /** @@ -229,7 +224,6 @@ public class PathImpl implements Path, Serializable { return; } } - final NodeImpl node; if ("<cross-parameter>".equals(name)) { node = new NodeImpl.CrossParameterNodeImpl(); @@ -237,7 +231,6 @@ public class PathImpl implements Path, Serializable { node = new NodeImpl.PropertyNodeImpl(name); } addNode(node); - } /** @@ -248,11 +241,10 @@ public class PathImpl implements Path, Serializable { * if no nodes are found */ public NodeImpl removeLeafNode() { - if (isRootPath() || nodeList.isEmpty()) { - throw new IllegalStateException("No nodes in path!"); - } + Exceptions.raiseIf(isRootPath() || nodeList.isEmpty(), IllegalStateException::new, "No nodes in path!"); + try { - return nodeList.remove(nodeList.size() - 1); + return nodeList.removeLast(); } finally { if (nodeList.isEmpty()) { nodeList.add(new NodeImpl((String) null)); @@ -269,7 +261,7 @@ public class PathImpl implements Path, Serializable { if (nodeList.isEmpty()) { return null; } - return (NodeImpl) nodeList.get(nodeList.size() - 1); + return nodeList.peekLast(); } /** @@ -292,14 +284,14 @@ public class PathImpl implements Path, Serializable { if (path instanceof PathImpl && ((PathImpl) path).isRootPath()) { return true; } - Iterator<Node> pathIter = path.iterator(); - Iterator<Node> thisIter = iterator(); + final Iterator<Node> pathIter = path.iterator(); + final Iterator<Node> thisIter = iterator(); while (pathIter.hasNext()) { - Node pathNode = pathIter.next(); + final Node pathNode = pathIter.next(); if (!thisIter.hasNext()) { return false; } - Node thisNode = thisIter.next(); + final Node thisNode = thisIter.next(); if (pathNode.isInIterable()) { if (!thisNode.isInIterable()) { return false; @@ -328,7 +320,7 @@ public class PathImpl implements Path, Serializable { */ @Override public String toString() { - StringBuilder builder = new StringBuilder(); + final StringBuilder builder = new StringBuilder(); for (Path.Node node : this) { NodeImpl.appendNode(node, builder); } @@ -346,9 +338,7 @@ public class PathImpl implements Path, Serializable { if (o == null || !getClass().equals(o.getClass())) { return false; } - - PathImpl path = (PathImpl) o; - return nodeList == path.nodeList || nodeList != null && nodeList.equals(path.nodeList); + return Objects.equals(nodeList, ((PathImpl) o).nodeList); } /** @@ -356,7 +346,6 @@ public class PathImpl implements Path, Serializable { */ @Override public int hashCode() { - return nodeList == null ? 0 : nodeList.hashCode(); + return Objects.hashCode(nodeList); } - } http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathNavigation.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathNavigation.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathNavigation.java index 36fb919..7ba6bc3 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathNavigation.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/PathNavigation.java @@ -18,12 +18,17 @@ package org.apache.bval.jsr.util; import javax.validation.ValidationException; +import org.apache.bval.util.Exceptions; import org.apache.commons.lang3.StringEscapeUtils; +import org.apache.commons.lang3.Validate; 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 java.util.logging.Logger; /** @@ -67,13 +72,13 @@ public class PathNavigation { /** * Callback "procedure" that always returns null. */ - public static abstract class CallbackProcedure implements Callback<Object> { + public static abstract class CallbackProcedure implements Callback<Void> { /** * {@inheritDoc} */ @Override - public final Object result() { + public final Void result() { complete(); return null; } @@ -85,6 +90,34 @@ public class PathNavigation { } } + 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(); @@ -118,8 +151,7 @@ public class PathNavigation { @Override protected void handleNextChar(CharSequence path, PathPosition pos, Writer target) throws IOException { - final int - codePoints = StringEscapeUtils.UNESCAPE_JAVA.translate(path, pos.getIndex(), target); + final int codePoints = StringEscapeUtils.UNESCAPE_JAVA.translate(path, pos.getIndex(), target); if (codePoints == 0) { super.handleNextChar(path, pos, target); } else { @@ -128,13 +160,12 @@ public class PathNavigation { } } } - } private static final Logger LOG = Logger.getLogger(PathNavigation.class.getName()); private static final QuotedStringParser QUOTED_STRING_PARSER; - + static { QuotedStringParser quotedStringParser; try { @@ -164,10 +195,10 @@ public class PathNavigation { public static <T> T navigateAndReturn(CharSequence propertyPath, Callback<? extends T> callback) { try { parse(propertyPath == null ? "" : propertyPath, new PathPosition(callback)); - } catch (ValidationException ex) { + } catch (ValidationException | IllegalArgumentException ex) { throw ex; - } catch (Exception ex) { - throw new ValidationException(String.format("invalid property: %s", propertyPath), ex); + } catch (Exception e) { + Exceptions.raise(ValidationException::new, e, "invalid property: %s", propertyPath); } return callback.result(); } @@ -190,23 +221,21 @@ public class PathNavigation { char c = path.charAt(here); switch (c) { case ']': - throw new IllegalStateException(String.format("Position %s: unexpected '%s'", here, c)); + Exceptions.raise(IllegalStateException::new, "Position %s: unexpected '%s'", here, c); case '[': handleIndex(path, pos.next()); break; case '.': - if (sep) { - throw new IllegalStateException( - String.format("Position %s: expected property, index/key, or end of expression", here)); - } + Exceptions.raiseIf(sep, IllegalStateException::new, + "Position %s: expected property, index/key, or end of expression", here); + sep = true; pos.next(); // fall through: default: - if (!sep) { - throw new IllegalStateException(String.format( - "Position %s: expected property path separator, index/key, or end of expression", here)); - } + 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; @@ -214,8 +243,8 @@ public class PathNavigation { } private static String parseProperty(CharSequence path, PathPosition pos) throws Exception { - int len = path.length(); - int start = pos.getIndex(); + final int len = path.length(); + final int start = pos.getIndex(); loop: while (pos.getIndex() < len) { switch (path.charAt(pos.getIndex())) { case '[': @@ -225,27 +254,28 @@ public class PathNavigation { } pos.next(); } - if (pos.getIndex() > start) { - return path.subSequence(start, pos.getIndex()).toString(); - } - throw new IllegalStateException(String.format("Position %s: expected property", start)); + 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. This function is only available if commons-lang3 is available on the classpath! + * string which may contain Java escape sequences. This function is only available if commons-lang3 is available on + * the classpath! * * @param path * @param pos * @throws Exception */ private static void handleIndex(CharSequence path, PathPosition pos) throws Exception { - int len = path.length(); - int start = pos.getIndex(); + final int len = path.length(); + final int start = pos.getIndex(); if (start < len) { - char first = path.charAt(pos.getIndex()); + final char first = path.charAt(pos.getIndex()); if (first == '"' || first == '\'') { - String s = QUOTED_STRING_PARSER.parseQuotedString(path, pos); + final String s = QUOTED_STRING_PARSER.parseQuotedString(path, pos); if (s != null && path.charAt(pos.getIndex()) == ']') { pos.handleIndexOrKey(s); pos.next(); @@ -254,7 +284,7 @@ public class PathNavigation { } // no quoted string; match ] greedily while (pos.getIndex() < len) { - int here = pos.getIndex(); + final int here = pos.getIndex(); try { if (path.charAt(here) == ']') { if (here == start) { @@ -269,13 +299,13 @@ public class PathNavigation { } } } - throw new IllegalStateException(String.format("Position %s: unparsable index", start)); + Exceptions.raise(IllegalStateException::new, "Position %s: unparsable index", start); } /** * ParsePosition/Callback */ - private static class PathPosition extends ParsePosition implements Callback<Object> { + private static class PathPosition extends ParsePosition implements Callback<Void> { final Callback<?> delegate; /** @@ -336,7 +366,7 @@ public class PathNavigation { * {@inheritDoc} */ @Override - public Object result() { + public Void result() { return null; } @@ -344,9 +374,8 @@ public class PathNavigation { * {@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 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) { @@ -364,5 +393,4 @@ public class PathNavigation { return super.hashCode(); } } - } http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/util/Proxies.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/Proxies.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/Proxies.java index 1fa033c..1d0c1ee 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/util/Proxies.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/Proxies.java @@ -26,7 +26,7 @@ public final class Proxies { private static final Set<String> KNOWN_PROXY_CLASSNAMES; static { - final Set<String> s = new HashSet<String>(); + final Set<String> s = new HashSet<>(); s.add("org.jboss.weld.bean.proxy.ProxyObject"); KNOWN_PROXY_CLASSNAMES = Collections.unmodifiableSet(s); } http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/util/ToUnmodifiable.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/ToUnmodifiable.java b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ToUnmodifiable.java new file mode 100644 index 0000000..a470e0d --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ToUnmodifiable.java @@ -0,0 +1,34 @@ +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 { + + public static <T> Collector<T, ?, Set<T>> set(Supplier<Set<T>> set) { + return Collectors.collectingAndThen(Collectors.toCollection(set), Collections::unmodifiableSet); + } + + public static <T> Collector<T, ?, Set<T>> set() { + return Collectors.collectingAndThen(Collectors.toCollection(LinkedHashSet::new), Collections::unmodifiableSet); + } + + public static <T> Collector<T, ?, List<T>> list() { + return Collectors.collectingAndThen(Collectors.toList(), Collections::unmodifiableList); + } + + 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), Collections::unmodifiableMap); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/FxExtractor.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/FxExtractor.java b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/FxExtractor.java new file mode 100644 index 0000000..15601b2 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/FxExtractor.java @@ -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()))); + } + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/IterableElementExtractor.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/IterableElementExtractor.java b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/IterableElementExtractor.java new file mode 100644 index 0000000..8fd2fc0 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/IterableElementExtractor.java @@ -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)); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ListElementExtractor.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ListElementExtractor.java b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ListElementExtractor.java new file mode 100644 index 0000000..bd84726 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ListElementExtractor.java @@ -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)); + } + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/MapExtractor.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/MapExtractor.java b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/MapExtractor.java new file mode 100644 index 0000000..a6848b8 --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/MapExtractor.java @@ -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())); + } + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/OptionalExtractor.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/OptionalExtractor.java b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/OptionalExtractor.java new file mode 100644 index 0000000..5f073cc --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/OptionalExtractor.java @@ -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); + } + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java new file mode 100644 index 0000000..50feb6c --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java @@ -0,0 +1,181 @@ +/* + * 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.Type; +import java.lang.reflect.TypeVariable; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; +import java.util.Set; +import java.util.function.BooleanSupplier; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +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.StringUtils; +import org.apache.bval.util.Validate; +import org.apache.bval.util.reflection.Reflection; +import org.apache.bval.util.reflection.TypeUtils; + +public class ValueExtractors { + public static final ValueExtractors DEFAULT; + + static { + DEFAULT = new ValueExtractors(null) { + { + final Properties defaultExtractors = new Properties(); + try { + defaultExtractors.load(ValueExtractors.class.getResourceAsStream("DefaultExtractors.properties")); + } catch (IOException e) { + throw new IllegalStateException(e); + } + 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(super::add); + + split(defaultExtractors.getProperty(ValueExtractor.class.getName() + ".container")) + .flatMap(ValueExtractors::loadValueExtractors).forEach(super::add); + } + + @Override + public void add(ValueExtractor<?> extractor) { + throw new UnsupportedOperationException(); + } + }; + } + + public static Class<?> getExtractedType(ValueExtractor<?> extractor, Type target) { + final ContainerElementKey key = ContainerElementKey.forValueExtractor(extractor); + Type result = key.getAnnotatedType().getType(); + if (result instanceof TypeVariable<?>) { + result = TypeUtils.getTypeArguments(target, key.getContainerClass()).get(result); + } + Exceptions.raiseUnless(result instanceof Class<?>, ValueExtractorDefinitionException::new, + "%s did not resolve to a %s relative to %s", key, Class.class.getName(), target); + return (Class<?>) result; + } + + private static Stream<String> split(String s) { + return Stream.of(StringUtils.split(s, ',')); + } + + private static <T> T newInstance(Class<T> t) { + try { + return t.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new IllegalStateException(e); + } + } + + 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 final Lazy<Map<ContainerElementKey, ValueExtractor<?>>> valueExtractors = new Lazy<>(HashMap::new); + private final ValueExtractors parent; + + public ValueExtractors() { + this(DEFAULT); + } + + private ValueExtractors(ValueExtractors parent) { + this.parent = parent; + } + + public ValueExtractors createChild() { + return new ValueExtractors(this); + } + + public void add(ValueExtractor<?> extractor) { + Validate.notNull(extractor); + valueExtractors.get().compute(ContainerElementKey.forValueExtractor(extractor), (k, v) -> { + Exceptions.raiseIf(v != null, ValueExtractorDeclarationException::new, + "Multiple context-level %ss specified for %s", ValueExtractor.class.getSimpleName(), k); + return extractor; + }); + } + + 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 Map<ContainerElementKey, ValueExtractor<?>> allValueExtractors = getValueExtractors(); + if (allValueExtractors.containsKey(key)) { + return allValueExtractors.get(key); + } + // search for assignable ContainerElementKey: + Set<ContainerElementKey> assignableKeys = key.getAssignableKeys(); + while (!assignableKeys.isEmpty()) { + final Optional<ValueExtractor<?>> found = assignableKeys.stream().filter(allValueExtractors::containsKey) + .<ValueExtractor<?>> map(allValueExtractors::get).findFirst(); + if (found.isPresent()) { + return found.get(); + } + assignableKeys = assignableKeys.stream().map(ContainerElementKey::getAssignableKeys) + .flatMap(Collection::stream).collect(Collectors.toSet()); + } + return null; + } + + protected void populate(Supplier<Map<ContainerElementKey, ValueExtractor<?>>> target) { + Optional.ofNullable(parent).ifPresent(p -> p.populate(target)); + valueExtractors.optional().ifPresent(m -> target.get().putAll(m)); + } +} http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxy.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxy.java b/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxy.java index 77aed70..96a2b46 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxy.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxy.java @@ -16,20 +16,21 @@ */ package org.apache.bval.jsr.xml; -import javax.validation.Valid; import java.io.Serializable; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Map; -import java.util.SortedSet; -import java.util.TreeSet; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Collectors; + +import javax.validation.Valid; + +import org.apache.bval.util.Exceptions; /** * Description: <br/> - * InvocationHandler implementation of <code>Annotation</code> that pretends it - * is a "real" source code annotation. + * InvocationHandler implementation of <code>Annotation</code> that pretends it is a "real" source code annotation. * <p/> */ class AnnotationProxy implements Annotation, InvocationHandler, Serializable { @@ -38,7 +39,7 @@ class AnnotationProxy implements Annotation, InvocationHandler, Serializable { private static final long serialVersionUID = 1L; private final Class<? extends Annotation> annotationType; - private final Map<String, Object> values; + private final SortedMap<String, Object> values; /** * Create a new AnnotationProxy instance. @@ -46,28 +47,23 @@ class AnnotationProxy implements Annotation, InvocationHandler, Serializable { * @param <A> * @param descriptor */ - public <A extends Annotation> AnnotationProxy(AnnotationProxyBuilder<A> descriptor) { + <A extends Annotation> AnnotationProxy(AnnotationProxyBuilder<A> descriptor) { this.annotationType = descriptor.getType(); - values = getAnnotationValues(descriptor); - } - - private <A extends Annotation> Map<String, Object> getAnnotationValues(AnnotationProxyBuilder<A> descriptor) { - final Map<String, Object> result = new HashMap<String, Object>(); + values = new TreeMap<>(); int processedValuesFromDescriptor = 0; for (final Method m : descriptor.getMethods()) { if (descriptor.contains(m.getName())) { - result.put(m.getName(), descriptor.getValue(m.getName())); + values.put(m.getName(), descriptor.getValue(m.getName())); processedValuesFromDescriptor++; - } else if (m.getDefaultValue() != null) { - result.put(m.getName(), m.getDefaultValue()); } else { - throw new IllegalArgumentException("No value provided for " + m.getName()); + Exceptions.raiseIf(m.getDefaultValue() == null, IllegalArgumentException::new, + "No value provided for %s", m.getName()); + values.put(m.getName(), m.getDefaultValue()); } } - if (processedValuesFromDescriptor != descriptor.size() && !Valid.class.equals(annotationType)) { - throw new RuntimeException("Trying to instanciate " + annotationType + " with unknown paramters."); - } - return result; + Exceptions.raiseUnless(processedValuesFromDescriptor == descriptor.size() || Valid.class.equals(annotationType), + IllegalArgumentException::new, "Trying to instantiate %s with unknown parameters.", + annotationType.getName()); } /** @@ -94,22 +90,7 @@ class AnnotationProxy implements Annotation, InvocationHandler, Serializable { */ @Override public String toString() { - StringBuilder result = new StringBuilder(); - result.append('@').append(annotationType().getName()).append('('); - boolean comma = false; - for (String m : getMethodsSorted()) { - if (comma) - result.append(", "); - result.append(m).append('=').append(values.get(m)); - comma = true; - } - result.append(")"); - return result.toString(); - } - - private SortedSet<String> getMethodsSorted() { - SortedSet<String> result = new TreeSet<String>(); - result.addAll(values.keySet()); - return result; + return values.entrySet().stream().map(e -> String.format("%s=%s", e.getKey(), e.getValue())) + .collect(Collectors.joining(", ", String.format("@%s(", annotationType().getName()), ")")); } } http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxyBuilder.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxyBuilder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxyBuilder.java index dedfabc..02383cb 100644 --- a/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxyBuilder.java +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/xml/AnnotationProxyBuilder.java @@ -30,10 +30,12 @@ import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import javax.enterprise.util.AnnotationLiteral; +import javax.validation.ConstraintTarget; import javax.validation.Payload; import javax.validation.Valid; import javax.validation.ValidationException; @@ -45,11 +47,21 @@ import javax.validation.groups.ConvertGroup; */ @Privilizing(@CallTo(Reflection.class)) public final class AnnotationProxyBuilder<A extends Annotation> { - private static final ConcurrentMap<Class<?>, Method[]> METHODS_CACHE = new ConcurrentHashMap<Class<?>, Method[]>(); + private static final ConcurrentMap<Class<?>, Method[]> METHODS_CACHE = new ConcurrentHashMap<>(); + + public static <A> Method[] findMethods(final Class<A> annotationType) { + // cache only built-in constraints to avoid memory leaks: + // TODO use configurable cache size property? + if (annotationType.getName().startsWith("javax.validation.constraints.")) { + return METHODS_CACHE.computeIfAbsent(annotationType, Reflection::getDeclaredMethods); + } + return Reflection.getDeclaredMethods(annotationType); + } private final Class<A> type; - private final Map<String, Object> elements = new HashMap<String, Object>(); + private final Map<String, Object> elements = new HashMap<>(); private final Method[] methods; + private boolean changed; /** * Create a new AnnotationProxyBuilder instance. @@ -61,21 +73,6 @@ public final class AnnotationProxyBuilder<A extends Annotation> { this.methods = findMethods(annotationType); } - public static <A> Method[] findMethods(final Class<A> annotationType) { - if (annotationType.getName().startsWith("javax.validation.constraints.")) { // cache built-in constraints only to avoid mem leaks - Method[] mtd = METHODS_CACHE.get(annotationType); - if (mtd == null) { - final Method[] value = Reflection.getDeclaredMethods(annotationType); - mtd = METHODS_CACHE.putIfAbsent(annotationType, value); - if (mtd == null) { - mtd = value; - } - } - return mtd; - } - return Reflection.getDeclaredMethods(annotationType); - } - /** * Create a new AnnotationProxyBuilder instance. * @@ -84,16 +81,15 @@ public final class AnnotationProxyBuilder<A extends Annotation> { */ public AnnotationProxyBuilder(Class<A> annotationType, Map<String, Object> elements) { this(annotationType); - for (Map.Entry<String, Object> entry : elements.entrySet()) { - this.elements.put(entry.getKey(), entry.getValue()); - } + elements.forEach(this.elements::put); } /** * Create a builder initially configured to create an annotation equivalent - * to <code>annot</code>. + * to {@code annot}. * - * @param annot Annotation to be replicated. + * @param annot + * Annotation to be replicated. */ @SuppressWarnings("unchecked") public AnnotationProxyBuilder(A annot) { @@ -102,8 +98,7 @@ public final class AnnotationProxyBuilder<A extends Annotation> { for (Method m : methods) { final boolean mustUnset = Reflection.setAccessible(m, true); try { - Object value = m.invoke(annot); - this.elements.put(m.getName(), value); + this.elements.put(m.getName(), m.invoke(annot)); } catch (Exception e) { throw new ValidationException("Cannot access annotation " + annot + " element: " + m.getName(), e); } finally { @@ -124,8 +119,22 @@ public final class AnnotationProxyBuilder<A extends Annotation> { * @param elementName * @param value */ - public void putValue(String elementName, Object value) { - elements.put(elementName, value); + @Deprecated + public Object putValue(String elementName, Object value) { + return elements.put(elementName, value); + } + + /** + * Add an element to the configuration. + * + * @param elementName + * @param value + * @return whether any change occurred + */ + public boolean setValue(String elementName, Object value) { + final boolean result = !Objects.equals(elements.put(elementName, value), value); + changed |= result; + return result; } /** @@ -171,27 +180,42 @@ public final class AnnotationProxyBuilder<A extends Annotation> { * Configure the well-known JSR303 "message" element. * * @param message + * @return */ - public void setMessage(String message) { - ConstraintAnnotationAttributes.MESSAGE.put(elements, message); + public boolean setMessage(String message) { + return setValue(ConstraintAnnotationAttributes.MESSAGE.getAttributeName(), message); } /** * Configure the well-known JSR303 "groups" element. * * @param groups + * @return */ - public void setGroups(Class<?>[] groups) { - ConstraintAnnotationAttributes.GROUPS.put(elements, groups); + public boolean setGroups(Class<?>[] groups) { + return setValue(ConstraintAnnotationAttributes.GROUPS.getAttributeName(), groups); } /** * Configure the well-known JSR303 "payload" element. * * @param payload + * @return + */ + public boolean setPayload(Class<? extends Payload>[] payload) { + return setValue(ConstraintAnnotationAttributes.PAYLOAD.getAttributeName(), payload); + } + + /** + * Configure the well-known "validationAppliesTo" element. + * + * @param constraintTarget */ - public void setPayload(Class<? extends Payload>[] payload) { - ConstraintAnnotationAttributes.PAYLOAD.put(elements, payload); + public boolean setValidationAppliesTo(ConstraintTarget constraintTarget) { + return setValue(ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO.getAttributeName(), constraintTarget); + } + public boolean isChanged() { + return changed; } /** @@ -203,16 +227,22 @@ public final class AnnotationProxyBuilder<A extends Annotation> { final ClassLoader classLoader = Reflection.getClassLoader(getType()); @SuppressWarnings("unchecked") final Class<A> proxyClass = (Class<A>) Proxy.getProxyClass(classLoader, getType()); - final InvocationHandler handler = new AnnotationProxy(this); - return doCreateAnnotation(proxyClass, handler); + return doCreateAnnotation(proxyClass, new AnnotationProxy(this)); } @Privileged private A doCreateAnnotation(final Class<A> proxyClass, final InvocationHandler handler) { try { - Constructor<A> constructor = proxyClass.getConstructor(InvocationHandler.class); - Reflection.setAccessible(constructor, true); // java 8 - return constructor.newInstance(handler); + final Constructor<A> constructor = proxyClass.getConstructor(InvocationHandler.class); + final boolean mustUnset = Reflection.setAccessible(constructor, true); // java + // 8 + try { + return constructor.newInstance(handler); + } finally { + if (mustUnset) { + Reflection.setAccessible(constructor, false); + } + } } catch (Exception e) { throw new ValidationException("Unable to create annotation for configured constraint", e); } http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/xml/SchemaManager.java ---------------------------------------------------------------------- diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/xml/SchemaManager.java b/bval-jsr/src/main/java/org/apache/bval/jsr/xml/SchemaManager.java new file mode 100644 index 0000000..a502b7e --- /dev/null +++ b/bval-jsr/src/main/java/org/apache/bval/jsr/xml/SchemaManager.java @@ -0,0 +1,286 @@ +/* + * 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.xml; + +import java.net.URL; +import java.util.Collections; +import java.util.Comparator; +import java.util.Objects; +import java.util.Optional; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.function.Function; +import java.util.logging.Level; +import java.util.logging.Logger; + +import javax.xml.XMLConstants; +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.UnmarshallerHandler; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; +import javax.xml.validation.ValidatorHandler; + +import org.apache.bval.util.Lazy; +import org.apache.bval.util.reflection.Reflection; +import org.w3c.dom.Document; +import org.xml.sax.Attributes; +import org.xml.sax.ContentHandler; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.XMLReader; +import org.xml.sax.helpers.AttributesImpl; +import org.xml.sax.helpers.XMLFilterImpl; + +/** + * Unmarshals XML converging on latest schema version. Presumes backward compatiblity between schemae. + */ +public class SchemaManager { + public static class Builder { + private final SortedMap<Key, Lazy<Schema>> data = new TreeMap<>(); + + public Builder add(String version, String ns, String resource) { + data.put(new Key(version, ns), new Lazy<>(() -> SchemaManager.loadSchema(resource))); + return this; + } + + public SchemaManager build() { + return new SchemaManager(new TreeMap<>(data)); + } + } + + private static class Key implements Comparable<Key> { + private static final Comparator<Key> CMP = Comparator.comparing(Key::getVersion).thenComparing(Key::getNs); + + final String version; + final String ns; + + Key(String version, String ns) { + super(); + this.version = Objects.toString(version, ""); + this.ns = Objects.toString(ns, ""); + } + + public String getVersion() { + return version; + } + + public String getNs() { + return ns; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + return Optional.ofNullable(obj).filter(SchemaManager.Key.class::isInstance) + .map(SchemaManager.Key.class::cast) + .filter(k -> Objects.equals(this.version, k.version) && Objects.equals(this.ns, k.ns)).isPresent(); + } + + @Override + public int hashCode() { + return Objects.hash(version, ns); + } + + @Override + public String toString() { + return String.format("%s:%s", version, ns); + } + + @Override + public int compareTo(Key o) { + return CMP.compare(this, o); + } + } + + private class DynamicValidatorHandler extends XMLFilterImpl { + ContentHandler ch; + SAXParseException e; + + @Override + public void setContentHandler(ContentHandler handler) { + super.setContentHandler(handler); + this.ch = handler; + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { + if (getContentHandler() == ch) { + final Key schemaKey = new Key(Objects.toString(atts.getValue("version"), ""), uri); + if (data.containsKey(schemaKey)) { + final Schema schema = data.get(schemaKey).get(); + final ValidatorHandler vh = schema.newValidatorHandler(); + vh.startDocument(); + vh.setContentHandler(ch); + super.setContentHandler(vh); + } + } + try { + super.startElement(uri, localName, qName, atts); + } catch (SAXParseException e) { + this.e = e; + } + } + + @Override + public void error(SAXParseException e) throws SAXException { + this.e = e; + super.error(e); + } + + @Override + public void fatalError(SAXParseException e) throws SAXException { + this.e = e; + super.fatalError(e); + } + + void validate() throws SAXParseException { + if (e != null) { + throw e; + } + } + } + + //@formatter:off + private enum XmlAttributeType { + CDATA, ID, IDREF, IDREFS, NMTOKEN, NMTOKENS, ENTITY, ENTITIES, NOTATION; + //@formatter:on + } + + private class SchemaRewriter extends XMLFilterImpl { + private boolean root = true; + + @Override + public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException { + final Key schemaKey = new Key(Objects.toString(atts.getValue("version"), ""), uri); + + if (!target.equals(schemaKey) && data.containsKey(schemaKey)) { + uri = target.ns; + if (root) { + atts = rewrite(atts); + root = false; + } + } + super.startElement(uri, localName, qName, atts); + } + + private Attributes rewrite(Attributes atts) { + final AttributesImpl result; + if (atts instanceof AttributesImpl) { + result = (AttributesImpl) atts; + } else { + result = new AttributesImpl(atts); + } + set(result, "", VERSION_ATTRIBUTE, "", XmlAttributeType.CDATA, target.version); + return result; + } + + private void set(AttributesImpl attrs, String uri, String localName, String qName, XmlAttributeType type, + String value) { + for (int i = 0, sz = attrs.getLength(); i < sz; i++) { + if (Objects.equals(qName, attrs.getQName(i)) + || Objects.equals(uri, attrs.getURI(i)) && Objects.equals(localName, attrs.getLocalName(i))) { + attrs.setAttribute(i, uri, localName, qName, type.name(), value); + return; + } + } + attrs.addAttribute(uri, localName, qName, type.name(), value); + } + } + + public static final String VERSION_ATTRIBUTE = "version"; + + private static final Logger log = Logger.getLogger(SchemaManager.class.getName()); + private static final SchemaFactory SCHEMA_FACTORY = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); + private static final SAXParserFactory SAX_PARSER_FACTORY; + + static { + SAX_PARSER_FACTORY = SAXParserFactory.newInstance(); + SAX_PARSER_FACTORY.setNamespaceAware(true); + } + + static Schema loadSchema(String resource) { + final URL schemaUrl = Reflection.getClassLoader(XmlUtils.class).getResource(resource); + try { + return SCHEMA_FACTORY.newSchema(schemaUrl); + } catch (SAXException e) { + log.log(Level.WARNING, String.format("Unable to parse schema: %s", resource), e); + return null; + } + } + + private static Class<?> getObjectFactory(Class<?> type) throws ClassNotFoundException { + final String className = String.format("%s.%s", type.getPackage().getName(), "ObjectFactory"); + return Reflection.toClass(className, type.getClassLoader()); + } + + private final Key target; + private final SortedMap<Key, Lazy<Schema>> data; + private final String description; + + private SchemaManager(SortedMap<Key, Lazy<Schema>> data) { + super(); + this.data = Collections.unmodifiableSortedMap(data); + this.target = data.lastKey(); + this.description = target.ns.substring(target.ns.lastIndexOf('/') + 1); + } + + public Optional<Schema> getSchema(String ns, String version) { + return Optional.of(new Key(version, ns)).map(data::get).map(Lazy::get); + } + + public Optional<Schema> getSchema(Document document) { + return Optional.ofNullable(document).map(Document::getDocumentElement) + .map(e -> getSchema(e.getAttribute(XMLConstants.XMLNS_ATTRIBUTE), e.getAttribute(VERSION_ATTRIBUTE))).get(); + } + + public <E extends Exception> Schema requireSchema(Document document, Function<String, E> exc) throws E { + return getSchema(document).orElseThrow(() -> Objects.requireNonNull(exc, "exc") + .apply(String.format("Unknown %s schema", Objects.toString(description, "")))); + } + + public <T> T unmarshal(InputSource input, Class<T> type) throws Exception { + final XMLReader xmlReader = SAX_PARSER_FACTORY.newSAXParser().getXMLReader(); + + // validate specified schema: + final DynamicValidatorHandler schemaValidator = new DynamicValidatorHandler(); + xmlReader.setContentHandler(schemaValidator); + + // rewrite to latest schema, if required: + final SchemaRewriter schemaRewriter = new SchemaRewriter(); + schemaValidator.setContentHandler(schemaRewriter); + + JAXBContext jc = JAXBContext.newInstance(getObjectFactory(type)); + // unmarshal: + final UnmarshallerHandler unmarshallerHandler = jc.createUnmarshaller().getUnmarshallerHandler(); + schemaRewriter.setContentHandler(unmarshallerHandler); + + xmlReader.parse(input); + + schemaValidator.validate(); + + @SuppressWarnings("unchecked") + final JAXBElement<T> result = (JAXBElement<T>) unmarshallerHandler.getResult(); + return result.getValue(); + } +}
