You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bval.apache.org by mb...@apache.org on 2018/03/21 15:45:44 UTC
bval git commit: ValueExtractor work for passing TCK
ValidatorResolutionTest
Repository: bval
Updated Branches:
refs/heads/bv2 4ed419e52 -> 54169ac59
ValueExtractor work for passing TCK ValidatorResolutionTest
Project: http://git-wip-us.apache.org/repos/asf/bval/repo
Commit: http://git-wip-us.apache.org/repos/asf/bval/commit/54169ac5
Tree: http://git-wip-us.apache.org/repos/asf/bval/tree/54169ac5
Diff: http://git-wip-us.apache.org/repos/asf/bval/diff/54169ac5
Branch: refs/heads/bv2
Commit: 54169ac5974b3ce1821fd266f152707bc86f3d30
Parents: 4ed419e
Author: Matt Benson <mb...@apache.org>
Authored: Wed Mar 21 10:45:31 2018 -0500
Committer: Matt Benson <mb...@apache.org>
Committed: Wed Mar 21 10:45:31 2018 -0500
----------------------------------------------------------------------
.../jsr/descriptor/ContainerElementTypeD.java | 64 +---------
.../org/apache/bval/jsr/job/ValidationJob.java | 88 +++++++++++--
.../bval/jsr/metadata/ContainerElementKey.java | 45 +++++--
.../java/org/apache/bval/jsr/metadata/Meta.java | 27 +---
.../apache/bval/jsr/metadata/XmlBuilder.java | 2 +-
.../bval/jsr/valueextraction/ExtractValues.java | 108 ++++++++++++++++
.../jsr/valueextraction/ValueExtractors.java | 62 ++++++---
.../apache/bval/util/EmulatedAnnotatedType.java | 125 +++++++++++++++++++
.../jsr/metadata/ContainerElementKeyTest.java | 34 ++++-
9 files changed, 434 insertions(+), 121 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/bval/blob/54169ac5/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ContainerElementTypeD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ContainerElementTypeD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ContainerElementTypeD.java
index cdae6f0..f8abed2 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ContainerElementTypeD.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ContainerElementTypeD.java
@@ -19,70 +19,21 @@
package org.apache.bval.jsr.descriptor;
import java.lang.reflect.AnnotatedType;
-import java.util.ArrayList;
-import java.util.List;
import java.util.stream.Stream;
import javax.validation.ConstraintDeclarationException;
-import javax.validation.ValidationException;
import javax.validation.metadata.ContainerElementTypeDescriptor;
import javax.validation.valueextraction.ValueExtractor;
import org.apache.bval.jsr.GraphContext;
import org.apache.bval.jsr.metadata.ContainerElementKey;
-import org.apache.bval.jsr.util.NodeImpl;
-import org.apache.bval.jsr.util.PathImpl;
+import org.apache.bval.jsr.valueextraction.ExtractValues;
import org.apache.bval.util.Exceptions;
-import org.apache.bval.util.Lazy;
import org.apache.bval.util.Validate;
public class ContainerElementTypeD extends CascadableContainerD<CascadableContainerD<?, ?>, AnnotatedType>
implements ContainerElementTypeDescriptor {
- private class Receiver implements ValueExtractor.ValueReceiver {
- private final GraphContext context;
- private Lazy<List<GraphContext>> result = new Lazy<>(ArrayList::new);
-
- Receiver(GraphContext context) {
- super();
- this.context = context;
- }
-
- @Override
- public void value(String nodeName, Object object) {
- addChild(new NodeImpl.ContainerElementNodeImpl(nodeName), object);
- }
-
- @Override
- public void iterableValue(String nodeName, Object object) {
- final NodeImpl node = new NodeImpl.ContainerElementNodeImpl(nodeName);
- node.setInIterable(true);
- addChild(node, object);
- }
-
- @Override
- public void indexedValue(String nodeName, int i, Object object) {
- final NodeImpl node = new NodeImpl.ContainerElementNodeImpl(nodeName);
- node.setIndex(Integer.valueOf(i));
- addChild(node, object);
- }
-
- @Override
- public void keyedValue(String nodeName, Object key, Object object) {
- final NodeImpl node = new NodeImpl.ContainerElementNodeImpl(nodeName);
- node.setKey(key);
- addChild(node, object);
- }
-
- private void addChild(NodeImpl node, Object value) {
- final PathImpl path = context.getPath();
- if (node.getName() != null) {
- path.addNode(node.inContainer(key.getContainerClass(), key.getTypeArgumentIndex()));
- }
- result.get().add(context.child(path, value));
- }
- }
-
private final ContainerElementKey key;
ContainerElementTypeD(ContainerElementKey key, MetadataReader.ForContainer<AnnotatedType> reader,
@@ -105,21 +56,12 @@ public class ContainerElementTypeD extends CascadableContainerD<CascadableContai
return key;
}
- @SuppressWarnings({ "rawtypes", "unchecked" })
@Override
protected Stream<GraphContext> readImpl(GraphContext context) throws Exception {
- final ValueExtractor valueExtractor = context.getValidatorContext().getValueExtractors().find(key);
+ final ValueExtractor<?> valueExtractor = context.getValidatorContext().getValueExtractors().find(key);
Exceptions.raiseIf(valueExtractor == null, ConstraintDeclarationException::new, "No %s found for %s",
ValueExtractor.class.getSimpleName(), key);
- final Receiver receiver = new Receiver(context);
- try {
- valueExtractor.extractValues(context.getValue(), receiver);
- } catch (ValidationException e) {
- throw e;
- } catch (Exception e) {
- throw new ValidationException(e);
- }
- return receiver.result.get().stream();
+ return ExtractValues.extract(context, key, valueExtractor).stream();
}
}
http://git-wip-us.apache.org/repos/asf/bval/blob/54169ac5/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java
index 2d4237c..3c74ce0 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java
@@ -22,6 +22,7 @@ import java.lang.reflect.Array;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Collections;
+import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
@@ -32,11 +33,13 @@ import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
+import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
+import javax.validation.ConstraintDeclarationException;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintViolation;
import javax.validation.ElementKind;
@@ -50,6 +53,8 @@ import javax.validation.metadata.CascadableDescriptor;
import javax.validation.metadata.ContainerDescriptor;
import javax.validation.metadata.ElementDescriptor.ConstraintFinder;
import javax.validation.metadata.PropertyDescriptor;
+import javax.validation.metadata.ValidateUnwrappedValue;
+import javax.validation.valueextraction.ValueExtractor;
import org.apache.bval.jsr.ApacheFactoryContext;
import org.apache.bval.jsr.ConstraintViolationImpl;
@@ -66,6 +71,8 @@ import org.apache.bval.jsr.metadata.ContainerElementKey;
import org.apache.bval.jsr.util.NodeImpl;
import org.apache.bval.jsr.util.PathImpl;
import org.apache.bval.jsr.util.Proxies;
+import org.apache.bval.jsr.valueextraction.ExtractValues;
+import org.apache.bval.jsr.valueextraction.ValueExtractors;
import org.apache.bval.util.Exceptions;
import org.apache.bval.util.Lazy;
import org.apache.bval.util.ObjectUtils;
@@ -103,9 +110,52 @@ public abstract class ValidationJob<T> {
abstract Object getBean();
- protected void validateDescriptorConstraints(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
+ void validateDescriptorConstraints(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
constraintsFrom(descriptor.findConstraints().unorderedAndMatchingGroups(group))
- .forEach(c -> validate(c, sink));
+ .forEach(c -> expand(c.getValueUnwrapping()).forEach(f -> f.validate(c, sink)));
+ }
+
+ private Stream<Frame<D>> expand(ValidateUnwrappedValue valueUnwrapping) {
+ if (valueUnwrapping != ValidateUnwrappedValue.SKIP && context.getValue() != null) {
+ final Optional<Map.Entry<ContainerElementKey, ValueExtractor<?>>> valueExtractorAndAssociatedContainerElementKey =
+ findValueExtractorAndAssociatedContainerElementKey(context.getValue().getClass(), valueUnwrapping);
+
+ if (valueExtractorAndAssociatedContainerElementKey.isPresent()) {
+ return ExtractValues
+ .extract(context, valueExtractorAndAssociatedContainerElementKey.get().getKey(),
+ valueExtractorAndAssociatedContainerElementKey.get().getValue())
+ .stream().map(child -> new UnwrappedElementConstraintValidationPseudoFrame<>(this, child));
+ }
+ }
+ return Stream.of(this);
+ }
+
+ private Optional<Map.Entry<ContainerElementKey, ValueExtractor<?>>> findValueExtractorAndAssociatedContainerElementKey(
+ Class<?> containerClass, ValidateUnwrappedValue valueUnwrapping) {
+ final Map<ContainerElementKey, ValueExtractor<?>> m = new HashMap<>();
+ final Predicate<ValueExtractor<?>> valueExtractorFilter =
+ x -> valueUnwrapping == ValidateUnwrappedValue.UNWRAP || ValueExtractors.isUnwrapByDefault(x);
+
+ final ContainerElementKey nonGenericKey = new ContainerElementKey(containerClass, null);
+
+ Optional.of(nonGenericKey).map(validatorContext.getValueExtractors()::find).filter(valueExtractorFilter)
+ .ifPresent(x -> m.put(nonGenericKey, x));
+
+ if (containerClass.getTypeParameters().length == 1) {
+ final ContainerElementKey genericKey = new ContainerElementKey(containerClass, Integer.valueOf(0));
+
+ Optional.of(genericKey).map(validatorContext.getValueExtractors()::find).filter(valueExtractorFilter)
+ .ifPresent(x -> m.put(genericKey, x));
+ }
+ if (m.isEmpty()) {
+ Exceptions.raiseIf(valueUnwrapping == ValidateUnwrappedValue.UNWRAP,
+ ConstraintDeclarationException::new, "No %s found for %s", containerClass);
+ return Optional.empty();
+ }
+ Exceptions.raiseIf(m.size() > 1, ConstraintDeclarationException::new,
+ "Found generic and non-generic %ss for %s", ValueExtractor.class.getSimpleName(), containerClass);
+
+ return Optional.of(m.entrySet().iterator().next());
}
@SuppressWarnings("unchecked")
@@ -126,11 +176,11 @@ public abstract class ValidationJob<T> {
// seen, ignore:
return true;
}
+ final ConstraintValidator constraintValidator = getConstraintValidator(constraint);
+
final ConstraintValidatorContextImpl<T> constraintValidatorContext =
new ConstraintValidatorContextImpl<>(this, constraint);
- final ConstraintValidator constraintValidator = getConstraintValidator(constraint);
-
final boolean valid;
if (constraintValidator == null) {
// null validator without exception implies composition:
@@ -144,9 +194,9 @@ public abstract class ValidationJob<T> {
} catch (Exception e) {
throw new ValidationException(e);
}
- }
- if (!valid) {
- constraintValidatorContext.getRequiredViolations().forEach(sink);
+ if (!valid) {
+ constraintValidatorContext.getRequiredViolations().forEach(sink);
+ }
}
if (valid || !constraint.isReportAsSingleViolation()) {
final boolean compositionValid = validateComposed(constraint, sink);
@@ -395,6 +445,30 @@ public abstract class ValidationJob<T> {
}
}
+ private class UnwrappedElementConstraintValidationPseudoFrame<D extends ElementD<?, ?>> extends Frame<D> {
+ final Lazy<IllegalStateException> exc = new Lazy<>(() -> Exceptions.create(IllegalStateException::new,
+ "%s is not meant to participate in validation lifecycle", getClass()));
+
+ UnwrappedElementConstraintValidationPseudoFrame(ValidationJob<T>.Frame<D> parent, GraphContext context) {
+ super(parent, parent.descriptor, context);
+ }
+
+ @Override
+ void validateDescriptorConstraints(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
+ throw exc.get();
+ }
+
+ @Override
+ void recurse(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
+ throw exc.get();
+ }
+
+ @Override
+ Object getBean() {
+ return parent.getBean();
+ }
+ }
+
protected static final TypeVariable<?> MAP_VALUE = Map.class.getTypeParameters()[1];
protected static final TypeVariable<?> ITERABLE_ELEMENT = Iterable.class.getTypeParameters()[0];
http://git-wip-us.apache.org/repos/asf/bval/blob/54169ac5/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ContainerElementKey.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ContainerElementKey.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ContainerElementKey.java
index acf1676..d7b5b18 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ContainerElementKey.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ContainerElementKey.java
@@ -19,6 +19,7 @@ package org.apache.bval.jsr.metadata;
import java.lang.reflect.AnnotatedParameterizedType;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.Collections;
import java.util.Comparator;
@@ -36,6 +37,7 @@ import javax.validation.valueextraction.ExtractedValue;
import javax.validation.valueextraction.ValueExtractor;
import javax.validation.valueextraction.ValueExtractorDefinitionException;
+import org.apache.bval.util.EmulatedAnnotatedType;
import org.apache.bval.util.Exceptions;
import org.apache.bval.util.Lazy;
import org.apache.bval.util.LazyInt;
@@ -43,6 +45,7 @@ import org.apache.bval.util.Validate;
import org.apache.bval.util.reflection.TypeUtils;
public class ContainerElementKey implements Comparable<ContainerElementKey> {
+
private static Logger log = Logger.getLogger(ContainerElementKey.class.getName());
public static ContainerElementKey forValueExtractor(ValueExtractor<?> extractor) {
@@ -57,10 +60,15 @@ public class ContainerElementKey implements Comparable<ContainerElementKey> {
final AnnotatedType containerType = decl.getAnnotatedActualTypeArguments()[0];
if (containerType.isAnnotationPresent(ExtractedValue.class)) {
- Exceptions.raiseIf(void.class.equals(containerType.getAnnotation(ExtractedValue.class).type()),
- ValueExtractorDefinitionException::new, "%s does not specify %s type for %s", extractorType,
- ExtractedValue.class.getSimpleName(), containerType);
- result.get().add(new ContainerElementKey(containerType, null));
+ final Class<?> extractedType = containerType.getAnnotation(ExtractedValue.class).type();
+ Exceptions.raiseIf(void.class.equals(extractedType), ValueExtractorDefinitionException::new,
+ "%s does not specify %s type for %s", extractorType, ExtractedValue.class.getSimpleName(),
+ containerType);
+ result.get().add(new ContainerElementKey(containerType, null) {
+ public AnnotatedType getAnnotatedType() {
+ return EmulatedAnnotatedType.wrap(extractedType);
+ }
+ });
}
Optional.of(containerType).filter(AnnotatedParameterizedType.class::isInstance)
.map(AnnotatedParameterizedType.class::cast)
@@ -105,6 +113,14 @@ public class ContainerElementKey implements Comparable<ContainerElementKey> {
.getAnnotatedActualTypeArguments()[typeArgumentIndex.intValue()];
}
+ public ContainerElementKey(Class<?> containerClass, Integer typeArgumentIndex) {
+ Validate.notNull(containerClass, "containerClass");
+ this.containerClass = containerClass;
+ this.typeArgumentIndex = validTypeArgumentIndex(typeArgumentIndex, containerClass);
+ this.annotatedType = typeArgumentIndex == null ? null
+ : EmulatedAnnotatedType.wrap(containerClass.getTypeParameters()[typeArgumentIndex.intValue()]);
+ }
+
public Class<?> getContainerClass() {
return containerClass;
}
@@ -114,15 +130,14 @@ public class ContainerElementKey implements Comparable<ContainerElementKey> {
}
public AnnotatedType getAnnotatedType() {
- return annotatedType;
+ return Optional.ofNullable(annotatedType).orElseThrow(UnsupportedOperationException::new);
}
@Override
public boolean equals(Object obj) {
return obj == this || Optional.ofNullable(obj).filter(ContainerElementKey.class::isInstance)
- .map(ContainerElementKey.class::cast)
- .filter(
- cek -> Objects.equals(containerClass, cek.containerClass) && typeArgumentIndex == cek.typeArgumentIndex)
+ .map(ContainerElementKey.class::cast).filter(cek -> Objects.equals(containerClass, cek.containerClass)
+ && Objects.equals(typeArgumentIndex, cek.typeArgumentIndex))
.isPresent();
}
@@ -139,7 +154,8 @@ public class ContainerElementKey implements Comparable<ContainerElementKey> {
@Override
public int compareTo(ContainerElementKey o) {
return Comparator.comparing(ContainerElementKey::containerClassName)
- .thenComparing(Comparator.comparing(ContainerElementKey::getTypeArgumentIndex)).compare(this, o);
+ .thenComparing(Comparator.nullsFirst(Comparator.comparing(ContainerElementKey::getTypeArgumentIndex)))
+ .compare(this, o);
}
public Set<ContainerElementKey> getAssignableKeys() {
@@ -155,11 +171,12 @@ public class ContainerElementKey implements Comparable<ContainerElementKey> {
}
private void hierarchy(Consumer<ContainerElementKey> sink) {
+ final TypeVariable<?> var;
if (typeArgumentIndex == null) {
- return;
+ var = null;
+ } else {
+ var = containerClass.getTypeParameters()[typeArgumentIndex.intValue()];
}
- final TypeVariable<?> var = containerClass.getTypeParameters()[typeArgumentIndex.intValue()];
-
final Lazy<Set<ContainerElementKey>> round = new Lazy<>(LinkedHashSet::new);
Stream
.concat(Stream.of(containerClass.getAnnotatedSuperclass()),
@@ -168,7 +185,8 @@ public class ContainerElementKey implements Comparable<ContainerElementKey> {
.forEach(t -> {
final AnnotatedType[] args = ((AnnotatedParameterizedType) t).getAnnotatedActualTypeArguments();
for (int i = 0; i < args.length; i++) {
- if (args[i].getType().equals(var)) {
+ final Type boundArgumentType = args[i].getType();
+ if (boundArgumentType instanceof Class<?> || boundArgumentType.equals(var)) {
round.get().add(new ContainerElementKey(t, Integer.valueOf(i)));
}
}
@@ -176,6 +194,7 @@ public class ContainerElementKey implements Comparable<ContainerElementKey> {
round.optional().ifPresent(s -> {
s.forEach(sink);
+ // recurse:
s.forEach(k -> k.hierarchy(sink));
});
}
http://git-wip-us.apache.org/repos/asf/bval/blob/54169ac5/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Meta.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Meta.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Meta.java
index 2ec80d2..913c1bf 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Meta.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Meta.java
@@ -18,7 +18,6 @@
*/
package org.apache.bval.jsr.metadata;
-import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.AnnotatedType;
@@ -33,6 +32,7 @@ import java.util.Objects;
import javax.validation.constraintvalidation.ValidationTarget;
+import org.apache.bval.util.EmulatedAnnotatedType;
import org.apache.bval.util.Lazy;
import org.apache.bval.util.Validate;
@@ -44,9 +44,11 @@ import org.apache.bval.util.Validate;
public abstract class Meta<E extends AnnotatedElement> {
public static class ForClass<T> extends Meta<Class<T>> {
+ private final AnnotatedType annotatedType;
public ForClass(Class<T> host) {
super(host, ElementType.TYPE);
+ this.annotatedType = EmulatedAnnotatedType.wrap(host);
}
@Override
@@ -61,28 +63,7 @@ public abstract class Meta<E extends AnnotatedElement> {
@Override
public AnnotatedType getAnnotatedType() {
- return new AnnotatedType() {
-
- @Override
- public Annotation[] getDeclaredAnnotations() {
- return getHost().getDeclaredAnnotations();
- }
-
- @Override
- public Annotation[] getAnnotations() {
- return getHost().getAnnotations();
- }
-
- @Override
- public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
- return getHost().getAnnotation(annotationClass);
- }
-
- @Override
- public Type getType() {
- return getHost();
- }
- };
+ return annotatedType;
}
@Override
http://git-wip-us.apache.org/repos/asf/bval/blob/54169ac5/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java
index 26d5224..a9cb7f4 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java
@@ -271,7 +271,7 @@ public class XmlBuilder {
"Missing required type argument index for %s", host);
typeArgumentIndex = Integer.valueOf(0);
}
- return new ContainerElementKey((AnnotatedParameterizedType) annotatedType, typeArgumentIndex);
+ return new ContainerElementKey(annotatedType, typeArgumentIndex);
}, XmlBuilder.ForContainerElementType::new));
}
Exceptions.raiseUnless(elements.isEmpty(), ValidationException::new,
http://git-wip-us.apache.org/repos/asf/bval/blob/54169ac5/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ExtractValues.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ExtractValues.java b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ExtractValues.java
new file mode 100644
index 0000000..cde4618
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ExtractValues.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.bval.jsr.valueextraction;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.validation.ValidationException;
+import javax.validation.valueextraction.ValueExtractor;
+
+import org.apache.bval.jsr.GraphContext;
+import org.apache.bval.jsr.metadata.ContainerElementKey;
+import org.apache.bval.jsr.util.NodeImpl;
+import org.apache.bval.jsr.util.PathImpl;
+import org.apache.bval.util.Lazy;
+import org.apache.bval.util.Validate;
+
+/**
+ * Utility class to extract values from a {@link GraphContext} using a {@link ValueExtractor}.
+ */
+public final class ExtractValues {
+
+ private static class Receiver implements ValueExtractor.ValueReceiver {
+ private final GraphContext context;
+ private final ContainerElementKey containerElementKey;
+ private final Lazy<List<GraphContext>> result = new Lazy<>(ArrayList::new);
+
+ Receiver(GraphContext context, ContainerElementKey containerElementKey) {
+ super();
+ this.context = context;
+ this.containerElementKey = containerElementKey;
+ }
+
+ @Override
+ public void value(String nodeName, Object object) {
+ addChild(new NodeImpl.ContainerElementNodeImpl(nodeName), object);
+ }
+
+ @Override
+ public void iterableValue(String nodeName, Object object) {
+ final NodeImpl node = new NodeImpl.ContainerElementNodeImpl(nodeName);
+ node.setInIterable(true);
+ addChild(node, object);
+ }
+
+ @Override
+ public void indexedValue(String nodeName, int i, Object object) {
+ final NodeImpl node = new NodeImpl.ContainerElementNodeImpl(nodeName);
+ node.setIndex(Integer.valueOf(i));
+ addChild(node, object);
+ }
+
+ @Override
+ public void keyedValue(String nodeName, Object key, Object object) {
+ final NodeImpl node = new NodeImpl.ContainerElementNodeImpl(nodeName);
+ node.setKey(key);
+ addChild(node, object);
+ }
+
+ private void addChild(NodeImpl node, Object value) {
+ final PathImpl path = context.getPath();
+ if (node.getName() != null) {
+ path.addNode(node.inContainer(containerElementKey.getContainerClass(),
+ containerElementKey.getTypeArgumentIndex()));
+ }
+ result.get().add(context.child(path, value));
+ }
+ }
+
+ private ExtractValues() {
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ public static List<GraphContext> extract(GraphContext context, ContainerElementKey containerElementKey,
+ ValueExtractor<?> valueExtractor) {
+ Validate.notNull(context, "context");
+ Validate.notNull(containerElementKey, "containerElementKey");
+ if (valueExtractor != null) {
+ final Receiver receiver = new Receiver(context, containerElementKey);
+ try {
+ ((ValueExtractor) valueExtractor).extractValues(context.getValue(), receiver);
+ } catch (ValidationException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ValidationException(e);
+ }
+ return receiver.result.optional().orElse(Collections.emptyList());
+ }
+ return Collections.singletonList(context);
+ }
+}
http://git-wip-us.apache.org/repos/asf/bval/blob/54169ac5/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java
index 3adc541..77af34a 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/valueextraction/ValueExtractors.java
@@ -19,8 +19,7 @@ package org.apache.bval.jsr.valueextraction;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Type;
-import java.lang.reflect.TypeVariable;
-import java.util.Collection;
+import java.lang.reflect.WildcardType;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -28,10 +27,14 @@ import java.util.Optional;
import java.util.Properties;
import java.util.Set;
import java.util.function.BooleanSupplier;
+import java.util.function.Function;
+import java.util.function.Predicate;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import javax.validation.ConstraintDeclarationException;
+import javax.validation.valueextraction.UnwrapByDefault;
import javax.validation.valueextraction.ValueExtractor;
import javax.validation.valueextraction.ValueExtractorDeclarationException;
import javax.validation.valueextraction.ValueExtractorDefinitionException;
@@ -42,6 +45,7 @@ import org.apache.bval.util.Lazy;
import org.apache.bval.util.StringUtils;
import org.apache.bval.util.Validate;
import org.apache.bval.util.reflection.Reflection;
+import org.apache.bval.util.reflection.Reflection.Interfaces;
import org.apache.bval.util.reflection.TypeUtils;
public class ValueExtractors {
@@ -80,16 +84,29 @@ public class ValueExtractors {
}
public static Class<?> getExtractedType(ValueExtractor<?> extractor, Type target) {
+
final ContainerElementKey key = ContainerElementKey.forValueExtractor(extractor);
Type result = key.getAnnotatedType().getType();
- if (result instanceof TypeVariable<?>) {
- result = TypeUtils.getTypeArguments(target, key.getContainerClass()).get(result);
+ if (result instanceof WildcardType && key.getTypeArgumentIndex() != null) {
+ result = TypeUtils.getTypeArguments(target, key.getContainerClass())
+ .get(key.getContainerClass().getTypeParameters()[key.getTypeArgumentIndex().intValue()]);
}
Exceptions.raiseUnless(result instanceof Class<?>, ValueExtractorDefinitionException::new,
"%s did not resolve to a %s relative to %s", key, Class.class.getName(), target);
return (Class<?>) result;
}
+ public static boolean isUnwrapByDefault(ValueExtractor<?> valueExtractor) {
+ if (valueExtractor != null) {
+ for (Class<?> t : Reflection.hierarchy(valueExtractor.getClass(), Interfaces.INCLUDE)) {
+ if (t.isAnnotationPresent(UnwrapByDefault.class)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
private static Stream<String> split(String s) {
return Stream.of(StringUtils.split(s, ','));
}
@@ -128,6 +145,10 @@ public class ValueExtractors {
}).map(ValueExtractors::newInstance);
}
+ private static boolean related(Class<?> c1, Class<?> c2) {
+ return c1.isAssignableFrom(c2) || c2.isAssignableFrom(c1);
+ }
+
private final Lazy<Map<ContainerElementKey, ValueExtractor<?>>> valueExtractors = new Lazy<>(HashMap::new);
private final ValueExtractors parent;
@@ -164,20 +185,31 @@ public class ValueExtractors {
return allValueExtractors.get(key);
}
// search for assignable ContainerElementKey:
- Set<ContainerElementKey> assignableKeys = key.getAssignableKeys();
- while (!assignableKeys.isEmpty()) {
- final Optional<ValueExtractor<?>> found = assignableKeys.stream().filter(allValueExtractors::containsKey)
- .<ValueExtractor<?>> map(allValueExtractors::get).findFirst();
- if (found.isPresent()) {
- return found.get();
- }
- assignableKeys = assignableKeys.stream().map(ContainerElementKey::getAssignableKeys)
- .flatMap(Collection::stream).collect(Collectors.toSet());
+ final Set<ContainerElementKey> assignableKeys = key.getAssignableKeys();
+ if (assignableKeys.isEmpty()) {
+ return null;
+ }
+ final Map<ContainerElementKey, ValueExtractor<?>> candidateMap =
+ assignableKeys.stream().filter(allValueExtractors::containsKey)
+ .collect(Collectors.toMap(Function.identity(), allValueExtractors::get));
+
+ if (candidateMap.isEmpty()) {
+ return null;
+ }
+ if (candidateMap.size() > 1) {
+ final Set<Class<?>> containerTypes =
+ candidateMap.keySet().stream().map(ContainerElementKey::getContainerClass).collect(Collectors.toSet());
+
+ final boolean allRelated = containerTypes.stream().allMatch(quid -> containerTypes.stream()
+ .filter(Predicate.isEqual(quid).negate()).allMatch(quo -> related(quid, quo)));
+
+ Exceptions.raiseUnless(allRelated, ConstraintDeclarationException::new,
+ "> 1 maximally specific %s found for %s", ValueExtractor.class.getSimpleName(), key);
}
- return null;
+ return candidateMap.values().iterator().next();
}
- protected void populate(Supplier<Map<ContainerElementKey, ValueExtractor<?>>> target) {
+ private void populate(Supplier<Map<ContainerElementKey, ValueExtractor<?>>> target) {
Optional.ofNullable(parent).ifPresent(p -> p.populate(target));
valueExtractors.optional().ifPresent(m -> target.get().putAll(m));
}
http://git-wip-us.apache.org/repos/asf/bval/blob/54169ac5/bval-jsr/src/main/java/org/apache/bval/util/EmulatedAnnotatedType.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/util/EmulatedAnnotatedType.java b/bval-jsr/src/main/java/org/apache/bval/util/EmulatedAnnotatedType.java
new file mode 100644
index 0000000..7df9655
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/util/EmulatedAnnotatedType.java
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.bval.util;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.AnnotatedParameterizedType;
+import java.lang.reflect.AnnotatedType;
+import java.lang.reflect.AnnotatedTypeVariable;
+import java.lang.reflect.AnnotatedWildcardType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+import java.util.Optional;
+import java.util.stream.Stream;
+
+public class EmulatedAnnotatedType<T extends Type> implements AnnotatedType {
+ private static class Parameterized extends EmulatedAnnotatedType<ParameterizedType>
+ implements AnnotatedParameterizedType {
+
+ Parameterized(ParameterizedType wrapped) {
+ super(wrapped);
+ }
+
+ @Override
+ public AnnotatedType[] getAnnotatedActualTypeArguments() {
+ return wrapArray(wrapped.getActualTypeArguments());
+ }
+ }
+
+ private static class Variable extends EmulatedAnnotatedType<TypeVariable<?>> implements AnnotatedTypeVariable {
+
+ Variable(TypeVariable<?> wrapped) {
+ super(wrapped);
+ }
+
+ @Override
+ public AnnotatedType[] getAnnotatedBounds() {
+ return wrapped.getAnnotatedBounds();
+ }
+ }
+
+ private static class Wildcard extends EmulatedAnnotatedType<WildcardType> implements AnnotatedWildcardType {
+
+ Wildcard(WildcardType wrapped) {
+ super(wrapped);
+ }
+
+ @Override
+ public AnnotatedType[] getAnnotatedLowerBounds() {
+ return wrapArray(wrapped.getLowerBounds());
+ }
+
+ @Override
+ public AnnotatedType[] getAnnotatedUpperBounds() {
+ return wrapArray(wrapped.getUpperBounds());
+ }
+ }
+
+ public static EmulatedAnnotatedType<?> wrap(Type type) {
+ if (type instanceof ParameterizedType) {
+ return new EmulatedAnnotatedType.Parameterized((ParameterizedType) type);
+ }
+ if (type instanceof TypeVariable<?>) {
+ return new EmulatedAnnotatedType.Variable((TypeVariable<?>) type);
+ }
+ if (type instanceof WildcardType) {
+ return new EmulatedAnnotatedType.Wildcard((WildcardType) type);
+ }
+ return new EmulatedAnnotatedType<>(type);
+ }
+
+ private static EmulatedAnnotatedType<?>[] wrapArray(Type[] types) {
+ return Stream.of(types).map(EmulatedAnnotatedType::wrap).toArray(EmulatedAnnotatedType[]::new);
+ }
+
+ private static final Annotation[] EMPTY_ANNOTATION_ARRAY = {};
+
+ protected final T wrapped;
+ private final Optional<AnnotatedElement> annotated;
+
+ private EmulatedAnnotatedType(T wrapped) {
+ super();
+ this.wrapped = Validate.notNull(wrapped);
+ this.annotated =
+ Optional.of(wrapped).filter(AnnotatedElement.class::isInstance).map(AnnotatedElement.class::cast);
+ }
+
+ @Override
+ public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
+ return annotated.map(e -> e.getAnnotation(annotationClass)).orElse(null);
+ }
+
+ @Override
+ public Annotation[] getAnnotations() {
+ return annotated.map(AnnotatedElement::getAnnotations).orElse(EMPTY_ANNOTATION_ARRAY);
+ }
+
+ @Override
+ public Annotation[] getDeclaredAnnotations() {
+ return annotated.map(AnnotatedElement::getDeclaredAnnotations).orElse(EMPTY_ANNOTATION_ARRAY);
+ }
+
+ @Override
+ public Type getType() {
+ return wrapped;
+ }
+}
http://git-wip-us.apache.org/repos/asf/bval/blob/54169ac5/bval-jsr/src/test/java/org/apache/bval/jsr/metadata/ContainerElementKeyTest.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/test/java/org/apache/bval/jsr/metadata/ContainerElementKeyTest.java b/bval-jsr/src/test/java/org/apache/bval/jsr/metadata/ContainerElementKeyTest.java
index b33b928..1fe2d9f 100644
--- a/bval-jsr/src/test/java/org/apache/bval/jsr/metadata/ContainerElementKeyTest.java
+++ b/bval-jsr/src/test/java/org/apache/bval/jsr/metadata/ContainerElementKeyTest.java
@@ -18,7 +18,9 @@
*/
package org.apache.bval.jsr.metadata;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import java.lang.reflect.Field;
import java.lang.reflect.TypeVariable;
@@ -34,6 +36,9 @@ public class ContainerElementKeyTest {
public List<String> strings;
}
+ public static abstract class BoundListType implements List<String> {
+ }
+
private Field stringsField;
@Before
@@ -75,6 +80,33 @@ public class ContainerElementKeyTest {
}
@Test
+ public void testAssignableKeysWithExplicitBinding() {
+ final ContainerElementKey containerElementKey = new ContainerElementKey(BoundListType.class, null);
+
+ final Iterator<ContainerElementKey> iterator = containerElementKey.getAssignableKeys().iterator();
+ {
+ assertTrue(iterator.hasNext());
+ final ContainerElementKey assignableKey = iterator.next();
+ assertEquals(List.class, assignableKey.getContainerClass());
+ assertEquals(0, assignableKey.getTypeArgumentIndex().intValue());
+ }
+ {
+ assertTrue(iterator.hasNext());
+ final ContainerElementKey assignableKey = iterator.next();
+ assertEquals(Collection.class, assignableKey.getContainerClass());
+ assertEquals(0, assignableKey.getTypeArgumentIndex().intValue());
+ }
+ {
+ assertTrue(iterator.hasNext());
+ final ContainerElementKey assignableKey = iterator.next();
+ assertEquals(Iterable.class, assignableKey.getContainerClass());
+ assertEquals(0, assignableKey.getTypeArgumentIndex().intValue());
+ assertTrue(assignableKey.getAnnotatedType().getType() instanceof TypeVariable<?>);
+ }
+ assertFalse(iterator.hasNext());
+ }
+
+ @Test
public void testTypeVariableInheritance() {
final ContainerElementKey containerElementKey =
new ContainerElementKey(stringsField.getAnnotatedType(), Integer.valueOf(0));