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/02/21 20:25:02 UTC
[06/11] bval git commit: implement BV 2.0 against existing BVal unit
tests
http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateProperty.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateProperty.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateProperty.java
new file mode 100644
index 0000000..a8fbdbc
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateProperty.java
@@ -0,0 +1,522 @@
+/*
+ * 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.job;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.Path;
+import javax.validation.metadata.BeanDescriptor;
+import javax.validation.metadata.CascadableDescriptor;
+import javax.validation.metadata.ContainerDescriptor;
+import javax.validation.metadata.ContainerElementTypeDescriptor;
+import javax.validation.metadata.ElementDescriptor;
+import javax.validation.metadata.PropertyDescriptor;
+import javax.validation.valueextraction.ValueExtractor;
+import javax.validation.valueextraction.ValueExtractor.ValueReceiver;
+
+import org.apache.bval.jsr.ApacheFactoryContext;
+import org.apache.bval.jsr.ConstraintViolationImpl;
+import org.apache.bval.jsr.GraphContext;
+import org.apache.bval.jsr.descriptor.BeanD;
+import org.apache.bval.jsr.descriptor.CascadableContainerD;
+import org.apache.bval.jsr.descriptor.ComposedD;
+import org.apache.bval.jsr.descriptor.ConstraintD;
+import org.apache.bval.jsr.descriptor.ContainerElementTypeD;
+import org.apache.bval.jsr.descriptor.ElementD;
+import org.apache.bval.jsr.descriptor.PropertyD;
+import org.apache.bval.jsr.metadata.ContainerElementKey;
+import org.apache.bval.jsr.util.PathImpl;
+import org.apache.bval.jsr.util.PathNavigation;
+import org.apache.bval.util.Exceptions;
+import org.apache.bval.util.ObjectWrapper;
+import org.apache.bval.util.StringUtils;
+import org.apache.bval.util.Validate;
+import org.apache.bval.util.reflection.TypeUtils;
+
+public final class ValidateProperty<T> extends ValidationJob<T> {
+
+ interface Strategy<T> {
+ default PathNavigation.Callback<?> callback(PathImpl.Builder pathBuilder, FindDescriptor findDescriptor) {
+ return new PathNavigation.CompositeCallbackProcedure(Arrays.asList(pathBuilder, findDescriptor));
+ }
+
+ default T getRootBean() {
+ return null;
+ }
+
+ ValidateProperty<T>.Frame<?> frame(ValidateProperty<T> job, PathImpl path);
+ }
+
+ static class ForBeanProperty<T> implements Strategy<T> {
+ final ApacheFactoryContext validatorContext;
+ final T rootBean;
+ final GraphContext rootContext;
+ final ObjectWrapper<GraphContext> leafContext;
+ final ObjectWrapper<Object> value;
+
+ ForBeanProperty(ApacheFactoryContext validatorContext, T bean) {
+ super();
+ this.validatorContext = validatorContext;
+ this.rootBean = bean;
+ this.rootContext = new GraphContext(validatorContext, PathImpl.create(), bean);
+ this.leafContext = new ObjectWrapper<>(rootContext);
+ this.value = new ObjectWrapper<>(bean);
+ }
+
+ @Override
+ public PathNavigation.Callback<?> callback(PathImpl.Builder pathBuilder, FindDescriptor findDescriptor) {
+ return new WalkGraph(validatorContext, pathBuilder, findDescriptor, value,
+ (p, v) -> leafContext.accept(p.isRootPath() ? rootContext : rootContext.child(p, v)));
+ }
+
+ @Override
+ public T getRootBean() {
+ return rootBean;
+ }
+
+ public GraphContext baseContext(PathImpl path, ApacheFactoryContext validatorContext) {
+ return new GraphContext(validatorContext, PathImpl.create(), rootBean).child(path, value.get());
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ public ValidationJob<T>.Frame<?> frame(ValidateProperty<T> job, PathImpl path) {
+ if (job.descriptor instanceof BeanDescriptor) {
+ return job.new LeafFrame(leafContext.get());
+ }
+ return job.new PropertyFrame(job.new BeanFrame(leafContext.get()), job.descriptor,
+ leafContext.get().child(path, value.get()));
+ }
+ }
+
+ static class ForPropertyValue<T> implements Strategy<T> {
+ final Object value;
+
+ ForPropertyValue(Object value) {
+ super();
+ this.value = value;
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ public ValidationJob<T>.Frame<?> frame(ValidateProperty<T> job, PathImpl path) {
+ final GraphContext context = new GraphContext(job.validatorContext, path, value);
+ if (job.descriptor instanceof BeanDescriptor) {
+ return job.new LeafFrame(context);
+ }
+ return job.new PropertyFrame(null, job.descriptor, context);
+ }
+ }
+
+ private interface Step {
+ Type type();
+
+ ElementD<?, ?> element();
+ }
+
+ private static class DescriptorWrapper implements Step {
+ final ElementD<?, ?> wrapped;
+
+ DescriptorWrapper(ElementDescriptor wrapped) {
+ super();
+ this.wrapped = (ElementD<?, ?>) wrapped;
+ }
+
+ @Override
+ public Type type() {
+ return wrapped.getGenericType();
+ }
+
+ @Override
+ public ElementD<?, ?> element() {
+ return wrapped;
+ }
+ }
+
+ private static class TypeWrapper implements Step {
+ final ApacheFactoryContext validatorContext;
+ final Type type;
+
+ TypeWrapper(ApacheFactoryContext validatorContext, Type type) {
+ super();
+ this.validatorContext = validatorContext;
+ this.type = type;
+ }
+
+ @Override
+ public Type type() {
+ return type;
+ }
+
+ @Override
+ public ElementD<?, ?> element() {
+ final Class<?> beanClass = TypeUtils.getRawType(type, null);
+ return beanClass == null ? null
+ : (BeanD) validatorContext.getDescriptorManager().getBeanDescriptor(beanClass);
+ }
+ }
+
+ private static class FindDescriptor implements PathNavigation.Callback<ElementD<?, ?>> {
+ private final ApacheFactoryContext validatorContext;
+ Step current;
+
+ FindDescriptor(ApacheFactoryContext validatorContext, Class<?> beanClass) {
+ this.validatorContext = validatorContext;
+ this.current = new DescriptorWrapper(validatorContext.getDescriptorManager().getBeanDescriptor(beanClass));
+ }
+
+ @Override
+ public void handleProperty(String name) {
+ final ElementDescriptor element = current.element();
+ final BeanD bean;
+ if (element instanceof BeanD) {
+ bean = (BeanD) element;
+ } else {
+ bean = (BeanD) validatorContext.getDescriptorManager().getBeanDescriptor(element.getElementClass());
+ }
+ final PropertyDescriptor property = bean.getProperty(name);
+ Exceptions.raiseIf(property == null, IllegalArgumentException::new, "Unknown property %s of %s", name,
+ bean.getElementClass());
+ current = new DescriptorWrapper(property);
+ }
+
+ @Override
+ public void handleIndexOrKey(String value) {
+ handleGenericInIterable();
+ }
+
+ @Override
+ public void handleGenericInIterable() {
+ final ElementDescriptor desc = current.element();
+ if (desc instanceof CascadableContainerD<?, ?>) {
+ final Step containerElement = handleContainerElement((CascadableContainerD<?, ?>) desc);
+ if (containerElement != null) {
+ current = containerElement;
+ return;
+ }
+ }
+ current = handleElementByType(current.type());
+ }
+
+ private Step handleContainerElement(CascadableContainerD<?, ?> desc) {
+ final Set<ContainerElementTypeDescriptor> containerElements = desc.getConstrainedContainerElementTypes();
+ if (containerElements.isEmpty()) {
+ return null;
+ }
+ final ContainerElementTypeDescriptor element;
+ if (containerElements.size() == 1) {
+ element = containerElements.iterator().next();
+ } else {
+ final Predicate<ContainerElementKey> wellKnown =
+ k -> k.represents(MAP_VALUE) || k.represents(ITERABLE_ELEMENT);
+
+ final Optional<ContainerElementTypeD> found =
+ containerElements.stream().map(ContainerElementTypeD.class::cast)
+ .filter(d -> d.getKey().getAssignableKeys().stream().anyMatch(wellKnown)).findFirst();
+
+ if (!found.isPresent()) {
+ return null;
+ }
+ element = found.get();
+ }
+ return new DescriptorWrapper(element);
+ }
+
+ private Step handleElementByType(Type type) {
+ Type elementType;
+
+ if (TypeUtils.isArrayType(type)) {
+ elementType = TypeUtils.getArrayComponentType(type);
+ } else if (TypeUtils.isAssignable(type, Map.class)) {
+ elementType =
+ Optional.ofNullable(TypeUtils.getTypeArguments(type, Map.class).get(MAP_VALUE)).orElse(MAP_VALUE);
+ } else if (TypeUtils.isAssignable(type, Iterable.class)) {
+ elementType =
+ Optional.ofNullable(TypeUtils.getTypeArguments(type, Iterable.class).get(ITERABLE_ELEMENT))
+ .orElse(ITERABLE_ELEMENT);
+ } else {
+ elementType = null;
+ }
+ Exceptions.raiseIf(elementType == null, IllegalArgumentException::new,
+ "Unable to resolve element type of %s", type);
+
+ return new TypeWrapper(validatorContext, elementType);
+ }
+
+ @Override
+ public ElementD<?, ?> result() {
+ return current.element();
+ }
+ }
+
+ private static class WalkGraph extends PathNavigation.CallbackProcedure {
+ final ApacheFactoryContext validatorContext;
+ final PathImpl.Builder pathBuilder;
+ final FindDescriptor findDescriptor;
+ final ObjectWrapper<Object> value;
+ final BiConsumer<PathImpl, Object> recordLeaf;
+
+ WalkGraph(ApacheFactoryContext validatorContext, PathImpl.Builder pathBuilder, FindDescriptor findDescriptor,
+ ObjectWrapper<Object> value, BiConsumer<PathImpl, Object> recordLeaf) {
+ this.validatorContext = validatorContext;
+ this.pathBuilder = pathBuilder;
+ this.findDescriptor = findDescriptor;
+ this.value = value;
+ this.recordLeaf = recordLeaf;
+ }
+
+ @Override
+ public void handleProperty(String name) {
+ final PathImpl p = PathImpl.copy(pathBuilder.result());
+ pathBuilder.handleProperty(name);
+ if (value.optional().isPresent()) {
+ recordLeaf.accept(p, value.get());
+
+ findDescriptor.handleProperty(name);
+
+ final PropertyD<?> propertyD =
+ ComposedD.unwrap(findDescriptor.current.element(), PropertyD.class).findFirst().get();
+ try {
+ value.accept(propertyD.getValue(value.get()));
+ } catch (Exception e) {
+ Exceptions.raise(IllegalStateException::new, e, "Unable to get value of property %s",
+ propertyD.getPropertyName());
+ }
+ }
+ }
+
+ @Override
+ public void handleIndexOrKey(final String indexOrKey) {
+ pathBuilder.handleIndexOrKey(indexOrKey);
+ findDescriptor.handleIndexOrKey(indexOrKey);
+ if (value.optional().isPresent()) {
+ ElementDescriptor element = findDescriptor.current.element();
+ if (element instanceof ContainerElementTypeD) {
+ value.accept(handleContainer(value.get(), ((ContainerElementTypeD) element).getKey(), indexOrKey));
+ } else {
+ value.accept(handleBasic(value.get(), indexOrKey));
+
+ if (element == null && value.optional().isPresent()) {
+ // no generic info available at some previous index level; fall back to runtime type of value
+ // and repair structure of findDescriptor:
+ findDescriptor.current = new TypeWrapper(validatorContext, value.get().getClass());
+ element = findDescriptor.current.element();
+ }
+ if (element instanceof BeanDescriptor) {
+ recordLeaf.accept(PathImpl.copy(pathBuilder.result()), value.get());
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private Object handleContainer(Object o, ContainerElementKey key, String indexOrKey) {
+ @SuppressWarnings("rawtypes")
+ final ValueExtractor valueExtractor = validatorContext.getValueExtractors().find(key);
+
+ final ObjectWrapper<Object> result = new ObjectWrapper<>();
+ valueExtractor.extractValues(o, new ValueReceiver() {
+
+ @Override
+ public void indexedValue(String nodeName, int index, Object object) {
+ if (Integer.toString(index).equals(indexOrKey)) {
+ result.accept(object);
+ }
+ }
+
+ @Override
+ public void iterableValue(String nodeName, Object object) {
+ // ?
+ result.accept(object);
+ }
+
+ @Override
+ public void keyedValue(String nodeName, Object key, Object object) {
+ if (String.valueOf(key).equals(indexOrKey)) {
+ result.accept(object);
+ }
+ }
+
+ @Override
+ public void value(String nodeName, Object object) {
+ // ?
+ result.accept(object);
+ }
+ });
+ return result.get();
+ }
+
+ private Object handleBasic(Object o, String indexOrKey) {
+ if (Map.class.isInstance(o)) {
+ for (Map.Entry<?, ?> e : ((Map<?, ?>) o).entrySet()) {
+ if (String.valueOf(e.getKey()).equals(indexOrKey)) {
+ return e.getValue();
+ }
+ }
+ } else {
+ try {
+ final int index = Integer.parseInt(indexOrKey);
+ Exceptions.raiseIf(index < 0, IllegalArgumentException::new, "Invalid index %d", index);
+ if (o != null && TypeUtils.isArrayType(o.getClass())) {
+ if (Array.getLength(o) > index) {
+ return Array.get(o, index);
+ }
+ } else if (List.class.isInstance(o)) {
+ final List<?> l = (List<?>) o;
+ if (l.size() > index) {
+ return l.get(index);
+ }
+ } else if (Iterable.class.isInstance(o)) {
+ int i = -1;
+ for (Object e : (Iterable<?>) o) {
+ if (++i == index) {
+ return e;
+ }
+ }
+ }
+ } catch (NumberFormatException e) {
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void handleGenericInIterable() {
+ throw new UnsupportedOperationException("Cannot resolve generic inIterable against actual object graph");
+ }
+ }
+
+ class LeafFrame extends BeanFrame {
+
+ LeafFrame(GraphContext context) {
+ super(context);
+ }
+
+ @Override
+ protected ValidationJob<T>.Frame<?> propertyFrame(PropertyD<?> d, GraphContext context) {
+ return new PropertyFrame<>(this, d, context);
+ }
+ }
+
+ class PropertyFrame<D extends ElementD<?, ?> & CascadableDescriptor & ContainerDescriptor> extends SproutFrame<D> {
+
+ PropertyFrame(ValidationJob<T>.Frame<?> parent, D descriptor, GraphContext context) {
+ super(parent, descriptor, context);
+ }
+
+ @Override
+ void recurse(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
+ if (cascade) {
+ super.recurse(group, sink);
+ }
+ }
+ }
+
+ private static final TypeVariable<?> MAP_VALUE = Map.class.getTypeParameters()[1];
+ private static final TypeVariable<?> ITERABLE_ELEMENT = Iterable.class.getTypeParameters()[0];
+
+ private final Strategy<T> strategy;
+ private final Class<T> rootBeanClass;
+ private final PathImpl propertyPath;
+ private final T rootBean;
+ private ElementD<?, ?> descriptor;
+ private boolean cascade;
+
+ private ValidateProperty(Strategy<T> strategy, ApacheFactoryContext validatorContext, Class<T> rootBeanClass,
+ String property, Class<?>[] groups) {
+ super(validatorContext, groups);
+
+ Exceptions.raiseIf(StringUtils.isBlank(property), IllegalArgumentException::new,
+ "property cannot be null/empty/blank");
+
+ this.strategy = strategy;
+ this.rootBeanClass = Validate.notNull(rootBeanClass, IllegalArgumentException::new, "rootBeanClass");
+
+ final PathImpl.Builder pathBuilder = new PathImpl.Builder();
+ final FindDescriptor findDescriptor = new FindDescriptor(validatorContext, rootBeanClass);
+
+ PathNavigation.navigate(property, strategy.callback(pathBuilder, findDescriptor));
+
+ this.propertyPath = pathBuilder.result();
+ this.descriptor = findDescriptor.result();
+ this.rootBean = strategy.getRootBean();
+ }
+
+ ValidateProperty(ApacheFactoryContext validatorContext, Class<T> rootBeanClass, String property, Object value,
+ Class<?>[] groups) {
+ this(new ForPropertyValue<>(value), validatorContext, rootBeanClass, property, groups);
+ if (descriptor == null) {
+ // should only occur when the root class is raw
+ descriptor = (ElementD<?, ?>) validatorContext.getDescriptorManager()
+ .getBeanDescriptor(value == null ? Object.class : value.getClass());
+ } else {
+ final Class<?> propertyType = descriptor.getElementClass();
+ Exceptions.raiseUnless(TypeUtils.isInstance(value, propertyType), IllegalArgumentException::new,
+ "%s is not an instance of %s", value, propertyType);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ ValidateProperty(ApacheFactoryContext validatorContext, T bean, String property, Class<?>[] groups)
+ throws Exception {
+ this(new ForBeanProperty<>(validatorContext, bean), validatorContext,
+ (Class<T>) Validate.notNull(bean, IllegalArgumentException::new, "bean").getClass(), property, groups);
+
+ Exceptions.raiseIf(descriptor == null, IllegalArgumentException::new,
+ "Could not resolve property name/path: %s", property);
+ }
+
+ public ValidateProperty<T> cascade(boolean cascade) {
+ this.cascade = cascade;
+ return this;
+ }
+
+ @Override
+ protected Frame<?> computeBaseFrame() {
+ // TODO assign bean as its own property and figure out what to do
+
+ return strategy.frame(this, propertyPath);
+ }
+
+ @Override
+ protected Class<T> getRootBeanClass() {
+ return rootBeanClass;
+ }
+
+ @Override
+ ConstraintViolationImpl<T> createViolation(String messageTemplate, ConstraintValidatorContextImpl<T> context,
+ Path propertyPath) {
+ final String message = validatorContext.getMessageInterpolator().interpolate(messageTemplate, context);
+
+ return new ConstraintViolationImpl<>(messageTemplate, message, rootBean, context.getFrame().getBean(),
+ propertyPath, context.getFrame().context.getValue(), context.getConstraintDescriptor(), rootBeanClass,
+ context.getConstraintDescriptor().unwrap(ConstraintD.class).getDeclaredOn(), null, null);
+ }
+}
http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java
new file mode 100644
index 0000000..e71e7ae
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateReturnValue.java
@@ -0,0 +1,126 @@
+package org.apache.bval.jsr.job;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+
+import javax.validation.Path;
+
+import org.apache.bval.jsr.ApacheFactoryContext;
+import org.apache.bval.jsr.ConstraintViolationImpl;
+import org.apache.bval.jsr.GraphContext;
+import org.apache.bval.jsr.descriptor.ConstraintD;
+import org.apache.bval.jsr.descriptor.ExecutableD;
+import org.apache.bval.jsr.descriptor.ReturnValueD;
+import org.apache.bval.jsr.metadata.Metas;
+import org.apache.bval.jsr.util.NodeImpl;
+import org.apache.bval.jsr.util.PathImpl;
+import org.apache.bval.util.Exceptions;
+import org.apache.bval.util.Validate;
+import org.apache.bval.util.reflection.TypeUtils;
+
+public abstract class ValidateReturnValue<E extends Executable, T> extends ValidationJob<T> {
+ public static class ForMethod<T> extends ValidateReturnValue<Method, T> {
+ private final T object;
+
+ ForMethod(ApacheFactoryContext validatorContext, T object, Method method, Object returnValue,
+ Class<?>[] groups) {
+ super(validatorContext,
+ new Metas.ForMethod(Validate.notNull(method, IllegalArgumentException::new, "method")), returnValue,
+ groups);
+ this.object = Validate.notNull(object, IllegalArgumentException::new, "object");
+ }
+
+ @Override
+ protected T getRootBean() {
+ return object;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected Class<T> getRootBeanClass() {
+ return (Class<T>) object.getClass();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected ExecutableD<Method, ?, ?> describe() {
+ return (ExecutableD<Method, ?, ?>) validatorContext.getDescriptorManager()
+ .getBeanDescriptor(object.getClass())
+ .getConstraintsForMethod(executable.getName(), executable.getParameterTypes());
+ }
+ }
+
+ public static class ForConstructor<T> extends ValidateReturnValue<Constructor<?>, T> {
+
+ ForConstructor(ApacheFactoryContext validatorContext, Constructor<? extends T> ctor, Object returnValue,
+ Class<?>[] groups) {
+ super(validatorContext,
+ new Metas.ForConstructor(Validate.notNull(ctor, IllegalArgumentException::new, "ctor")), returnValue,
+ groups);
+ }
+
+ @Override
+ protected T getRootBean() {
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected Class<T> getRootBeanClass() {
+ return (Class<T>) executable.getDeclaringClass();
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ protected ExecutableD<Constructor<T>, ?, ?> describe() {
+ return (ExecutableD<Constructor<T>, ?, ?>) validatorContext.getDescriptorManager()
+ .getBeanDescriptor(executable.getDeclaringClass())
+ .getConstraintsForConstructor(executable.getParameterTypes());
+ }
+ }
+
+ protected final E executable;
+ private final Object returnValue;
+
+ ValidateReturnValue(ApacheFactoryContext validatorContext, Metas<E> meta, Object returnValue, Class<?>[] groups) {
+ super(validatorContext, groups);
+
+ final Type type = Validate.notNull(meta, "meta").getType();
+ Exceptions.raiseUnless(TypeUtils.isInstance(returnValue, type), IllegalArgumentException::new,
+ "%s is not an instance of %s", returnValue, type);
+
+ this.executable = meta.getHost();
+ this.returnValue = returnValue;
+ }
+
+ @Override
+ protected Frame<?> computeBaseFrame() {
+ final PathImpl path = PathImpl.create();
+ path.addNode(new NodeImpl.ReturnValueNodeImpl());
+
+ return new SproutFrame<ReturnValueD<?, ?>>((ReturnValueD<?, ?>) describe().getReturnValueDescriptor(),
+ new GraphContext(validatorContext, path, returnValue)) {
+ @Override
+ Object getBean() {
+ return getRootBean();
+ }
+ };
+ }
+
+ @Override
+ ConstraintViolationImpl<T> createViolation(String messageTemplate, ConstraintValidatorContextImpl<T> context,
+ Path propertyPath) {
+
+ final String message = validatorContext.getMessageInterpolator().interpolate(messageTemplate, context);
+
+ return new ConstraintViolationImpl<>(messageTemplate, message, getRootBean(), context.getFrame().getBean(),
+ propertyPath, context.getFrame().context.getValue(), context.getConstraintDescriptor(), getRootBeanClass(),
+ context.getConstraintDescriptor().unwrap(ConstraintD.class).getDeclaredOn(), returnValue, null);
+ }
+
+ protected abstract ExecutableD<?, ?, ?> describe();
+
+ protected abstract T getRootBean();
+}
http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/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
new file mode 100644
index 0000000..9221184
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java
@@ -0,0 +1,380 @@
+/*
+ * 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.job;
+
+import java.lang.reflect.Array;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentSkipListSet;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintViolation;
+import javax.validation.Path;
+import javax.validation.TraversableResolver;
+import javax.validation.UnexpectedTypeException;
+import javax.validation.ValidationException;
+import javax.validation.groups.Default;
+import javax.validation.metadata.CascadableDescriptor;
+import javax.validation.metadata.ContainerDescriptor;
+import javax.validation.metadata.ElementDescriptor.ConstraintFinder;
+import javax.validation.metadata.PropertyDescriptor;
+
+import org.apache.bval.jsr.ApacheFactoryContext;
+import org.apache.bval.jsr.ConstraintViolationImpl;
+import org.apache.bval.jsr.GraphContext;
+import org.apache.bval.jsr.descriptor.BeanD;
+import org.apache.bval.jsr.descriptor.CascadableContainerD;
+import org.apache.bval.jsr.descriptor.ComposedD;
+import org.apache.bval.jsr.descriptor.ConstraintD;
+import org.apache.bval.jsr.descriptor.ElementD;
+import org.apache.bval.jsr.descriptor.PropertyD;
+import org.apache.bval.jsr.groups.Group;
+import org.apache.bval.jsr.groups.Groups;
+import org.apache.bval.jsr.util.NodeImpl;
+import org.apache.bval.jsr.util.PathImpl;
+import org.apache.bval.util.Exceptions;
+import org.apache.bval.util.Lazy;
+import org.apache.bval.util.Validate;
+
+public abstract class ValidationJob<T> {
+
+ public abstract class Frame<D extends ElementD<?, ?>> {
+ protected final Frame<?> parent;
+ protected final D descriptor;
+ protected final GraphContext context;
+
+ protected Frame(Frame<?> parent, D descriptor, GraphContext context) {
+ super();
+ this.parent = parent;
+ this.descriptor = Validate.notNull(descriptor, "descriptor");
+ this.context = Validate.notNull(context, "context");
+ }
+
+ final ValidationJob<T> getJob() {
+ return ValidationJob.this;
+ }
+
+ final void process(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
+ Validate.notNull(sink, "sink");
+
+ each(expand(group), this::validateDescriptorConstraints, sink);
+ recurse(group, sink);
+ }
+
+ abstract void recurse(Class<?> group, Consumer<ConstraintViolation<T>> sink);
+
+ abstract Object getBean();
+
+ protected void validateDescriptorConstraints(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
+ constraintsFrom(descriptor.findConstraints().unorderedAndMatchingGroups(group))
+ .forEach(c -> validate(c, sink));
+ }
+
+ @SuppressWarnings("unchecked")
+ private Stream<ConstraintD<?>> constraintsFrom(ConstraintFinder finder) {
+ // our ConstraintFinder implementation is a Stream supplier; reference without exposing it beyond its
+ // package:
+ if (finder instanceof Supplier<?>) {
+ return (Stream<ConstraintD<?>>) ((Supplier<?>) finder).get();
+ }
+ return finder.getConstraintDescriptors().stream().map(ConstraintD.class::cast);
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ private boolean validate(ConstraintD<?> constraint, Consumer<ConstraintViolation<T>> sink) {
+ if (!validatedPathsByConstraint
+ .computeIfAbsent(constraint, k -> new ConcurrentSkipListSet<>(COMPARE_TO_STRING))
+ .add(context.getPath())) {
+ // seen, ignore:
+ return true;
+ }
+ 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:
+ valid = true;
+ } else {
+ constraintValidator.initialize(constraint.getAnnotation());
+ valid = constraintValidator.isValid(context.getValue(), constraintValidatorContext);
+ }
+ if (!valid) {
+ constraintValidatorContext.getRequiredViolations().forEach(sink);
+ }
+ if (valid || !constraint.isReportAsSingleViolation()) {
+ final boolean compositionValid = validateComposed(constraint, sink);
+
+ if (!compositionValid) {
+ if (valid && constraint.isReportAsSingleViolation()) {
+ constraintValidatorContext.getRequiredViolations().forEach(sink);
+ }
+ return false;
+ }
+ }
+ return valid;
+ }
+
+ private boolean validateComposed(ConstraintD<?> constraint, Consumer<ConstraintViolation<T>> sink) {
+ if (constraint.getComposingConstraints().isEmpty()) {
+ return true;
+ }
+ final Consumer<ConstraintViolation<T>> effectiveSink = constraint.isReportAsSingleViolation() ? cv -> {
+ } : sink;
+
+ // collect validation results to set of Boolean, ensuring all are evaluated:
+ final Set<Boolean> results = constraint.getComposingConstraints().stream().map(ConstraintD.class::cast)
+ .map(c -> validate(c, effectiveSink)).collect(Collectors.toSet());
+
+ return Collections.singleton(Boolean.TRUE).equals(results);
+ }
+
+ @SuppressWarnings({ "rawtypes" })
+ private ConstraintValidator getConstraintValidator(ConstraintD<?> constraint) {
+ final Class<? extends ConstraintValidator> constraintValidatorClass =
+ constraint.getConstraintValidatorClass();
+
+ if (constraintValidatorClass == null) {
+ Exceptions.raiseIf(constraint.getComposingConstraints().isEmpty(), UnexpectedTypeException::new,
+ "No %s type located for non-composed constraint %s", ConstraintValidator.class.getSimpleName(),
+ constraint);
+ return null;
+ }
+ ConstraintValidator constraintValidator = null;
+ Exception cause = null;
+ try {
+ constraintValidator =
+ validatorContext.getConstraintValidatorFactory().getInstance(constraintValidatorClass);
+ } catch (Exception e) {
+ cause = e;
+ }
+ Exceptions.raiseIf(constraintValidator == null, ValidationException::new, cause,
+ "Unable to get %s instance from %s", constraintValidatorClass.getName(),
+ validatorContext.getConstraintValidatorFactory());
+
+ return constraintValidator;
+ }
+
+ protected Stream<Class<?>> expand(Class<?> group) {
+ if (Default.class.equals(group)) {
+ final List<Class<?>> groupSequence = descriptor.getGroupSequence();
+ if (groupSequence != null) {
+ return groupSequence.stream();
+ }
+ }
+ return Stream.of(group);
+ }
+ }
+
+ public class BeanFrame extends Frame<BeanD> {
+
+ BeanFrame(GraphContext context) {
+ this(null, context);
+ }
+
+ BeanFrame(Frame<?> parent, GraphContext context) {
+ super(parent, getBeanDescriptor(context.getValue()), context);
+ }
+
+ @Override
+ void recurse(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
+ // bean frame has to do some convoluted things to properly handle groups and recursion; skipping
+ // frame#process() on properties:
+ final List<Frame<?>> propertyFrames = propertyFrames();
+
+ each(expand(group), (g, s) -> propertyFrames.forEach(f -> f.validateDescriptorConstraints(g, s)), sink);
+ propertyFrames.forEach(f -> f.recurse(group, sink));
+ }
+
+ protected Frame<?> propertyFrame(PropertyD<?> d, GraphContext context) {
+ return new SproutFrame<>(this, d, context);
+ }
+
+ @Override
+ Object getBean() {
+ return context.getValue();
+ }
+
+ private List<Frame<?>> propertyFrames() {
+ final Stream<PropertyD<?>> properties = descriptor.getConstrainedProperties().stream()
+ .flatMap(d -> ComposedD.unwrap(d, PropertyD.class)).map(d -> (PropertyD<?>) d);
+
+ final TraversableResolver traversableResolver = validatorContext.getTraversableResolver();
+
+ final Stream<PropertyD<?>> reachableProperties =
+ properties.filter(d -> traversableResolver.isReachable(context.getValue(),
+ new NodeImpl.PropertyNodeImpl(d.getPropertyName()), getRootBeanClass(), context.getPath(),
+ d.getElementType()));
+
+ return reachableProperties.flatMap(
+ d -> d.read(context).filter(context -> !context.isRecursive()).map(child -> propertyFrame(d, child)))
+ .collect(Collectors.toList());
+ }
+ }
+
+ public class SproutFrame<D extends ElementD<?, ?> & CascadableDescriptor & ContainerDescriptor> extends Frame<D> {
+
+ public SproutFrame(D descriptor, GraphContext context) {
+ this(null, descriptor, context);
+ }
+
+ public SproutFrame(Frame<?> parent, D descriptor, GraphContext context) {
+ super(parent, descriptor, context);
+ }
+
+ @Override
+ void recurse(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ final Stream<CascadableContainerD<?, ?>> containerElements =
+ descriptor.getConstrainedContainerElementTypes().stream()
+ .flatMap(d -> ComposedD.unwrap(d, (Class) CascadableContainerD.class));
+
+ containerElements.flatMap(d -> d.read(context).map(child -> new SproutFrame<>(this, d, child)))
+ .forEach(f -> f.process(group, sink));
+
+ if (!descriptor.isCascaded()) {
+ return;
+ }
+ if (descriptor instanceof PropertyDescriptor) {
+ final TraversableResolver traversableResolver = validatorContext.getTraversableResolver();
+
+ final PathImpl pathToTraversableObject = PathImpl.copy(context.getPath());
+ final NodeImpl traversableProperty = pathToTraversableObject.removeLeafNode();
+
+ if (!traversableResolver.isCascadable(context.getValue(), traversableProperty, getRootBeanClass(),
+ pathToTraversableObject, ((PropertyD<?>) descriptor).getElementType())) {
+ return;
+ }
+ }
+ multiplex().filter(context -> context.getValue() != null).map(context -> new BeanFrame(this, context))
+ .forEach(b -> b.process(group, sink));
+ }
+
+ private Stream<GraphContext> multiplex() {
+ final Object value = context.getValue();
+ if (value == null) {
+ return Stream.empty();
+ }
+ if (Map.class.isInstance(value)) {
+ return ((Map<?, ?>) value).entrySet().stream()
+ .map(e -> context.child(NodeImpl.atKey(e.getKey()), e.getValue()));
+ }
+ if (value.getClass().isArray()) {
+ return IntStream.range(0, Array.getLength(value))
+ .mapToObj(i -> context.child(NodeImpl.atIndex(i), Array.get(value, i)));
+ }
+ if (List.class.isInstance(value)) {
+ final List<?> l = (List<?>) value;
+ return IntStream.range(0, l.size()).mapToObj(i -> context.child(NodeImpl.atIndex(i), l.get(i)));
+ }
+ if (Iterable.class.isInstance(value)) {
+ final Stream.Builder<Object> b = Stream.builder();
+ ((Iterable<?>) value).forEach(b);
+ return b.build().map(o -> context.child(NodeImpl.atIndex(null), o));
+ }
+ return Stream.of(context);
+ }
+
+ @Override
+ Object getBean() {
+ return Optional.ofNullable(parent).map(Frame::getBean).orElse(null);
+ }
+ }
+
+ private static final Comparator<Path> COMPARE_TO_STRING = Comparator.comparing(Object::toString);
+
+ protected final ApacheFactoryContext validatorContext;
+
+ private final Groups groups;
+ private final Lazy<Set<ConstraintViolation<T>>> results = new Lazy<>(LinkedHashSet::new);
+
+ private ConcurrentMap<ConstraintD<?>, Set<Path>> validatedPathsByConstraint;
+
+ ValidationJob(ApacheFactoryContext validatorContext, Class<?>[] groups) {
+ super();
+ this.validatorContext = Validate.notNull(validatorContext, "validatorContext");
+ this.groups = validatorContext.getGroupsComputer().computeGroups(groups);
+ }
+
+ public final Set<ConstraintViolation<T>> getResults() {
+ if (results.optional().isPresent()) {
+ return results.get();
+ }
+ final Frame<?> baseFrame = computeBaseFrame();
+ Validate.validState(baseFrame != null, "%s computed null baseFrame", getClass().getName());
+
+ final Consumer<ConstraintViolation<T>> sink = results.consumer(Set::add);
+
+ validatedPathsByConstraint = new ConcurrentHashMap<>();
+
+ try {
+ groups.getGroups().stream().map(Group::getGroup).forEach(g -> baseFrame.process(g, sink));
+
+ sequences: for (List<Group> seq : groups.getSequences()) {
+ final boolean proceed = each(seq.stream().map(Group::getGroup), baseFrame::process, sink);
+ if (!proceed) {
+ break sequences;
+ }
+ }
+ } finally {
+ validatedPathsByConstraint = null;
+ }
+ return results.optional().map(Collections::unmodifiableSet).orElse(Collections.emptySet());
+ }
+
+ private boolean each(Stream<Class<?>> groupSequence, BiConsumer<Class<?>, Consumer<ConstraintViolation<T>>> closure,
+ Consumer<ConstraintViolation<T>> sink) {
+ final Lazy<Set<ConstraintViolation<T>>> sequenceViolations = new Lazy<>(LinkedHashSet::new);
+ for (Class<?> g : (Iterable<Class<?>>) () -> groupSequence.iterator()) {
+ closure.accept(g, sequenceViolations.consumer(Set::add));
+ if (sequenceViolations.optional().isPresent()) {
+ sequenceViolations.get().forEach(sink);
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private BeanD getBeanDescriptor(Object bean) {
+ return (BeanD) validatorContext.getFactory().getDescriptorManager()
+ .getBeanDescriptor(Validate.notNull(bean, "bean").getClass());
+ }
+
+ abstract ConstraintViolationImpl<T> createViolation(String messageTemplate,
+ ConstraintValidatorContextImpl<T> context, Path propertyPath);
+
+ protected abstract Frame<?> computeBaseFrame();
+
+ protected abstract Class<T> getRootBeanClass();
+}
http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJobFactory.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJobFactory.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJobFactory.java
new file mode 100644
index 0000000..2a23192
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJobFactory.java
@@ -0,0 +1,112 @@
+/*
+ * 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.job;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+
+import javax.validation.ValidationException;
+import javax.validation.Validator;
+import javax.validation.executable.ExecutableValidator;
+
+import org.apache.bval.jsr.ApacheFactoryContext;
+import org.apache.bval.util.Validate;
+
+/**
+ * Creates {@link ValidationJob} instances.
+ */
+public class ValidationJobFactory {
+
+ private final ApacheFactoryContext validatorContext;
+
+ /**
+ * Create a new {@link ValidationJobFactory}.
+ *
+ * @param validatorContext
+ */
+ public ValidationJobFactory(ApacheFactoryContext validatorContext) {
+ super();
+ this.validatorContext = Validate.notNull(validatorContext, "validatorContext");
+ }
+
+ /**
+ * @see Validator#validate(Object, Class...)
+ */
+ public <T> ValidateBean<T> validateBean(T bean, Class<?>... groups) {
+ return new ValidateBean<>(validatorContext, bean, groups);
+ }
+
+ /**
+ * @see Validator#validateProperty(Object, String, Class...)
+ */
+ public <T> ValidateProperty<T> validateProperty(T bean, String property, Class<?>... groups) {
+ try {
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ final ValidateProperty<T> result = new ValidateProperty(validatorContext, bean, property, groups);
+ return result;
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ValidationException(e);
+ }
+ }
+
+ /**
+ * @see Validator#validateValue(Class, String, Object, Class...)
+ */
+ public <T> ValidateProperty<T> validateValue(Class<T> rootBeanClass, String property, Object value,
+ Class<?>... groups) {
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ final ValidateProperty<T> result =
+ new ValidateProperty(validatorContext, rootBeanClass, property, value, groups);
+ return result;
+ }
+
+ /**
+ * @see ExecutableValidator#validateParameters(Object, Method, Object[], Class...)
+ */
+ public <T> ValidateParameters.ForMethod<T> validateParameters(T object, Method method, Object[] parameterValues,
+ Class<?>... groups) {
+ return new ValidateParameters.ForMethod<T>(validatorContext, object, method, parameterValues, groups);
+ }
+
+ /**
+ * @see ExecutableValidator#validateReturnValue(Object, Method, Object, Class...)
+ */
+ public <T> ValidateReturnValue.ForMethod<T> validateReturnValue(T object, Method method, Object returnValue,
+ Class<?>... groups) {
+ return new ValidateReturnValue.ForMethod<>(validatorContext, object, method, returnValue, groups);
+ }
+
+ /**
+ * @see ExecutableValidator#validateConstructorParameters(Constructor, Object[], Class...)
+ */
+ public <T> ValidateParameters.ForConstructor<T> validateConstructorParameters(Constructor<? extends T> constructor,
+ Object[] parameterValues, Class<?>... groups) {
+ return new ValidateParameters.ForConstructor<T>(validatorContext, constructor, parameterValues, groups);
+ }
+
+ /**
+ * @see ExecutableValidator#validateConstructorReturnValue(Constructor, Object, Class...)
+ */
+ public <T> ValidateReturnValue.ForConstructor<T> validateConstructorReturnValue(
+ Constructor<? extends T> constructor, T createdObject, Class<?>... groups) {
+ return new ValidateReturnValue.ForConstructor<T>(validatorContext, constructor, createdObject, groups);
+ }
+}
http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehavior.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehavior.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehavior.java
new file mode 100644
index 0000000..56ed4f0
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehavior.java
@@ -0,0 +1,35 @@
+/*
+ * 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.metadata;
+
+import org.apache.bval.jsr.metadata.MetadataBuilder;
+
+/**
+ * Models the behavior of a {@link MetadataBuilder} with regard to bean validation annotations.
+ *
+ * @see DualBuilder
+ */
+public enum AnnotationBehavior implements AnnotationBehaviorMergeStrategy {
+ //@formatter:off
+ INCLUDE, EXCLUDE, ABSTAIN;
+ //@formatter:on
+
+ @Override
+ public AnnotationBehavior apply(Iterable<? extends HasAnnotationBehavior> t) {
+ return this;
+ }
+}
http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehaviorMergeStrategy.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehaviorMergeStrategy.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehaviorMergeStrategy.java
new file mode 100644
index 0000000..bfd16c5
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationBehaviorMergeStrategy.java
@@ -0,0 +1,54 @@
+/*
+ * 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.metadata;
+
+import java.util.EnumSet;
+import java.util.Iterator;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.apache.bval.util.Validate;
+
+@FunctionalInterface
+public interface AnnotationBehaviorMergeStrategy
+ extends Function<Iterable<? extends HasAnnotationBehavior>, AnnotationBehavior> {
+
+ public static AnnotationBehaviorMergeStrategy first() {
+ return coll -> {
+ final Iterator<? extends HasAnnotationBehavior> iterator = coll.iterator();
+ return iterator.hasNext() ? iterator.next().getAnnotationBehavior() : AnnotationBehavior.ABSTAIN;
+ };
+ }
+
+ public static AnnotationBehaviorMergeStrategy consensus() {
+ return coll -> {
+ final Stream.Builder<HasAnnotationBehavior> b = Stream.builder();
+ coll.forEach(b);
+ final Set<AnnotationBehavior> annotationBehaviors =
+ b.build().map(HasAnnotationBehavior::getAnnotationBehavior).filter(Objects::nonNull)
+ .filter(Predicate.isEqual(AnnotationBehavior.ABSTAIN).negate())
+ .collect(Collectors.toCollection(() -> EnumSet.noneOf(AnnotationBehavior.class)));
+ Validate.validState(annotationBehaviors.size() <= 1,
+ "Conflicting annotation inclusion behaviors found among %s", coll);
+ return annotationBehaviors.isEmpty() ? AnnotationBehavior.ABSTAIN : annotationBehaviors.iterator().next();
+ };
+ }
+}
http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationDeclaredValidatorMappingProvider.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationDeclaredValidatorMappingProvider.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationDeclaredValidatorMappingProvider.java
new file mode 100644
index 0000000..b2126ac
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/AnnotationDeclaredValidatorMappingProvider.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.metadata;
+
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
+import java.util.List;
+
+import javax.validation.Constraint;
+import javax.validation.ConstraintValidator;
+
+import org.apache.bval.util.Validate;
+
+public class AnnotationDeclaredValidatorMappingProvider extends ValidatorMappingProvider {
+ public static final AnnotationDeclaredValidatorMappingProvider INSTANCE =
+ new AnnotationDeclaredValidatorMappingProvider();
+
+ @Override
+ protected <A extends Annotation> ValidatorMapping<A> doGetValidatorMapping(Class<A> constraintType) {
+ Validate.notNull(constraintType);
+ Validate.isTrue(constraintType.isAnnotationPresent(Constraint.class),
+ "%s does not represent a validation constraint", constraintType);
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ final List<Class<? extends ConstraintValidator<A, ?>>> validatorTypes =
+ (List) Arrays.asList(constraintType.getAnnotation(Constraint.class).validatedBy());
+ return new ValidatorMapping<>("@Constraint.validatedBy()", validatorTypes);
+ }
+}
http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ClassLoadingValidatorMappingProvider.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ClassLoadingValidatorMappingProvider.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ClassLoadingValidatorMappingProvider.java
new file mode 100644
index 0000000..e636a8a
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ClassLoadingValidatorMappingProvider.java
@@ -0,0 +1,48 @@
+/*
+ * 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.metadata;
+
+import java.util.Objects;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+import org.apache.bval.util.reflection.Reflection;
+import org.apache.commons.weaver.privilizer.Privilizing;
+import org.apache.commons.weaver.privilizer.Privilizing.CallTo;
+
+@Privilizing(@CallTo(Reflection.class))
+public abstract class ClassLoadingValidatorMappingProvider extends ValidatorMappingProvider {
+
+ protected final <T> Stream<Class<? extends T>> load(Stream<String> classNames, Class<T> assignableTo,
+ Consumer<? super ClassNotFoundException> handleException) {
+ return classNames.map(className -> {
+ try {
+ return Reflection.toClass(className, getClassLoader());
+ } catch (ClassNotFoundException e) {
+ handleException.accept(e);
+ return (Class<?>) null;
+ }
+ }).filter(Objects::nonNull).map(c -> (Class<? extends T>) c.asSubclass(assignableTo));
+ }
+
+ protected ClassLoader getClassLoader() {
+ final ClassLoader classloader = Thread.currentThread().getContextClassLoader();
+ return classloader == null ? getClass().getClassLoader() : classloader;
+ }
+}
http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeBuilder.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeBuilder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeBuilder.java
new file mode 100644
index 0000000..52a7407
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeBuilder.java
@@ -0,0 +1,227 @@
+/*
+ * 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.metadata;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.AnnotatedType;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import javax.validation.metadata.Scope;
+
+import org.apache.bval.jsr.descriptor.GroupConversion;
+import org.apache.bval.jsr.util.ToUnmodifiable;
+import org.apache.bval.util.Validate;
+
+public class CompositeBuilder {
+
+ class Delegator<DELEGATE extends HasAnnotationBehavior> implements HasAnnotationBehavior {
+
+ protected final List<DELEGATE> delegates;
+
+ Delegator(List<DELEGATE> delegates) {
+ this.delegates = Validate.notNull(delegates, "delegates");
+ Validate.isTrue(!delegates.isEmpty(), "no delegates specified");
+ Validate.isTrue(delegates.stream().noneMatch(Objects::isNull), "One or more supplied delegates was null");
+ }
+
+ @Override
+ public AnnotationBehavior getAnnotationBehavior() {
+ return annotationBehaviorStrategy.apply(delegates);
+ }
+
+ <K, D> Map<K, D> merge(Function<DELEGATE, Map<K, D>> toMap, Function<List<D>, D> merge) {
+ final List<Map<K, D>> maps = delegates.stream().map(toMap).collect(Collectors.toList());
+
+ final Function<? super K, ? extends D> valueMapper = k -> {
+ final List<D> mappedDelegates =
+ maps.stream().map(m -> m.get(k)).filter(Objects::nonNull).collect(Collectors.toList());
+ return mappedDelegates.size() == 1 ? mappedDelegates.get(0) : merge.apply(mappedDelegates);
+ };
+
+ return maps.stream().map(Map::keySet).flatMap(Collection::stream).distinct()
+ .collect(Collectors.toMap(Function.identity(), valueMapper));
+ }
+ }
+
+ private class ForBean extends CompositeBuilder.Delegator<MetadataBuilder.ForBean>
+ implements MetadataBuilder.ForBean {
+
+ ForBean(List<MetadataBuilder.ForBean> delegates) {
+ super(delegates);
+ }
+
+ @Override
+ public MetadataBuilder.ForClass getClass(Metas<Class<?>> meta) {
+ return new CompositeBuilder.ForClass(
+ delegates.stream().map(d -> d.getClass(meta)).collect(Collectors.toList()));
+ }
+
+ @Override
+ public Map<String, MetadataBuilder.ForContainer<Field>> getFields(Metas<Class<?>> meta) {
+ return merge(b -> b.getFields(meta), CompositeBuilder.ForContainer::new);
+ }
+
+ @Override
+ public Map<String, MetadataBuilder.ForContainer<Method>> getGetters(Metas<Class<?>> meta) {
+ return merge(b -> b.getGetters(meta), CompositeBuilder.ForContainer::new);
+ }
+
+ @Override
+ public Map<Signature, MetadataBuilder.ForExecutable<Constructor<?>>> getConstructors(Metas<Class<?>> meta) {
+ return merge(b -> b.getConstructors(meta), CompositeBuilder.ForExecutable::new);
+ }
+
+ @Override
+ public Map<Signature, MetadataBuilder.ForExecutable<Method>> getMethods(Metas<Class<?>> meta) {
+ return merge(b -> b.getMethods(meta), CompositeBuilder.ForExecutable::new);
+ }
+ }
+
+ class ForElement<DELEGATE extends MetadataBuilder.ForElement<E>, E extends AnnotatedElement>
+ extends Delegator<DELEGATE> implements MetadataBuilder.ForElement<E> {
+
+ ForElement(List<DELEGATE> delegates) {
+ super(delegates);
+ }
+
+ @Override
+ public Map<Scope, Annotation[]> getConstraintsByScope(Metas<E> meta) {
+ return CompositeBuilder.this.getConstraintsByScope(this, meta);
+ }
+
+ @Override
+ public final Annotation[] getDeclaredConstraints(Metas<E> meta) {
+ return delegates.stream().map(d -> d.getDeclaredConstraints(meta)).flatMap(Stream::of)
+ .toArray(Annotation[]::new);
+ }
+ }
+
+ class ForClass extends ForElement<MetadataBuilder.ForClass, Class<?>> implements MetadataBuilder.ForClass {
+
+ ForClass(List<MetadataBuilder.ForClass> delegates) {
+ super(delegates);
+ }
+
+ @Override
+ public List<Class<?>> getGroupSequence(Metas<Class<?>> meta) {
+ return CompositeBuilder.this.getGroupSequence(this, meta);
+ }
+ }
+
+ private class ForContainer<DELEGATE extends MetadataBuilder.ForContainer<E>, E extends AnnotatedElement>
+ extends CompositeBuilder.ForElement<DELEGATE, E> implements MetadataBuilder.ForContainer<E> {
+
+ ForContainer(List<DELEGATE> delegates) {
+ super(delegates);
+ }
+
+ @Override
+ public final boolean isCascade(Metas<E> meta) {
+ return delegates.stream().anyMatch(d -> d.isCascade(meta));
+ }
+
+ @Override
+ public final Set<GroupConversion> getGroupConversions(Metas<E> meta) {
+ return delegates.stream().map(d -> d.getGroupConversions(meta)).flatMap(Collection::stream)
+ .collect(ToUnmodifiable.set());
+ }
+
+ @Override
+ public final Map<ContainerElementKey, MetadataBuilder.ForContainer<AnnotatedType>> getContainerElementTypes(
+ Metas<E> meta) {
+ return merge(b -> b.getContainerElementTypes(meta), CompositeBuilder.ForContainer::new);
+ }
+ }
+
+ private class ForExecutable<DELEGATE extends MetadataBuilder.ForExecutable<E>, E extends Executable>
+ extends Delegator<DELEGATE> implements MetadataBuilder.ForExecutable<E> {
+
+ ForExecutable(List<DELEGATE> delegates) {
+ super(delegates);
+ }
+
+ @Override
+ public MetadataBuilder.ForContainer<E> getReturnValue(Metas<E> meta) {
+ return new CompositeBuilder.ForContainer<>(
+ delegates.stream().map(d -> d.getReturnValue(meta)).collect(Collectors.toList()));
+ }
+
+ @Override
+ public List<MetadataBuilder.ForContainer<Parameter>> getParameters(Metas<E> meta) {
+ final List<List<MetadataBuilder.ForContainer<Parameter>>> parameterLists =
+ delegates.stream().map(d -> d.getParameters(meta)).collect(Collectors.toList());
+
+ final Set<Integer> parameterCounts = parameterLists.stream().map(List::size).collect(Collectors.toSet());
+ Validate.validState(parameterCounts.size() == 1, "Mismatched parameter counts: %s", parameterCounts);
+
+ return IntStream.range(0, parameterCounts.iterator().next().intValue())
+ .mapToObj(n -> new CompositeBuilder.ForContainer<>(parameterLists.get(n)))
+ .collect(ToUnmodifiable.list());
+ }
+
+ @Override
+ public MetadataBuilder.ForElement<E> getCrossParameter(Metas<E> meta) {
+ return new CompositeBuilder.ForElement<MetadataBuilder.ForElement<E>, E>(
+ delegates.stream().map(d -> d.getCrossParameter(meta)).collect(Collectors.toList()));
+ }
+ }
+
+ public static CompositeBuilder with(AnnotationBehaviorMergeStrategy annotationBehaviorStrategy) {
+ return new CompositeBuilder(annotationBehaviorStrategy);
+ }
+
+ private final AnnotationBehaviorMergeStrategy annotationBehaviorStrategy;
+
+ CompositeBuilder(AnnotationBehaviorMergeStrategy annotationBehaviorMergeStrategy) {
+ super();
+ this.annotationBehaviorStrategy =
+ Validate.notNull(annotationBehaviorMergeStrategy, "annotationBehaviorMergeStrategy");
+ }
+
+ public Collector<MetadataBuilder.ForBean, ?, MetadataBuilder.ForBean> compose() {
+ return Collectors.collectingAndThen(Collectors.toList(), CompositeBuilder.ForBean::new);
+ }
+
+ protected <E extends AnnotatedElement> Map<Scope, Annotation[]> getConstraintsByScope(
+ CompositeBuilder.ForElement<? extends MetadataBuilder.ForElement<E>, E> composite, Metas<E> meta) {
+ return Collections.singletonMap(Scope.LOCAL_ELEMENT, composite.getDeclaredConstraints(meta));
+ }
+
+ protected List<Class<?>> getGroupSequence(CompositeBuilder.ForClass composite, Metas<Class<?>> meta) {
+ final List<List<Class<?>>> groupSequence =
+ composite.delegates.stream().map(d -> d.getGroupSequence(meta)).collect(Collectors.toList());
+ Validate.validState(groupSequence.size() <= 1,
+ "group sequence returned from multiple composite class metadata builders");
+ return groupSequence.isEmpty() ? null : groupSequence.get(0);
+ }
+}
http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeValidatorMappingProvider.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeValidatorMappingProvider.java b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeValidatorMappingProvider.java
new file mode 100644
index 0000000..9808f89
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/CompositeValidatorMappingProvider.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.metadata;
+
+import java.lang.annotation.Annotation;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import org.apache.bval.util.Validate;
+
+public class CompositeValidatorMappingProvider extends ValidatorMappingProvider {
+
+ private final List<ValidatorMappingProvider> delegates;
+
+ public CompositeValidatorMappingProvider(List<ValidatorMappingProvider> delegates) {
+ super();
+ this.delegates = Validate.notNull(delegates, "delegates");
+ Validate.isTrue(!delegates.isEmpty(), "no delegates specified");
+ Validate.isTrue(delegates.stream().noneMatch(Objects::isNull), "One or more supplied delegates was null");
+ }
+
+ @Override
+ protected <A extends Annotation> ValidatorMapping<A> doGetValidatorMapping(Class<A> constraintType) {
+ return ValidatorMapping.merge(delegates.stream().map(d -> d.doGetValidatorMapping(constraintType))
+ .filter(Objects::nonNull).collect(Collectors.toList()), AnnotationBehaviorMergeStrategy.consensus());
+ }
+}
http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/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
new file mode 100644
index 0000000..322a4ef
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/ContainerElementKey.java
@@ -0,0 +1,175 @@
+/*
+ * 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.metadata;
+
+import java.lang.reflect.AnnotatedParameterizedType;
+import java.lang.reflect.AnnotatedType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.TypeVariable;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.logging.Logger;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import javax.validation.valueextraction.ExtractedValue;
+import javax.validation.valueextraction.ValueExtractor;
+import javax.validation.valueextraction.ValueExtractorDefinitionException;
+
+import org.apache.bval.util.Exceptions;
+import org.apache.bval.util.Lazy;
+import org.apache.bval.util.LazyInt;
+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) {
+ @SuppressWarnings("rawtypes")
+ final Class<? extends ValueExtractor> extractorType = extractor.getClass();
+ final Lazy<Set<ContainerElementKey>> result = new Lazy<>(HashSet::new);
+
+ Stream.of(extractorType.getAnnotatedInterfaces()).filter(AnnotatedParameterizedType.class::isInstance)
+ .map(AnnotatedParameterizedType.class::cast)
+ .filter(apt -> ValueExtractor.class.equals(((ParameterizedType) apt.getType()).getRawType()))
+ .forEach(decl -> {
+ 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));
+ }
+ Optional.of(containerType).filter(AnnotatedParameterizedType.class::isInstance)
+ .map(AnnotatedParameterizedType.class::cast)
+ .map(AnnotatedParameterizedType::getAnnotatedActualTypeArguments).ifPresent(args -> {
+ IntStream.range(0, args.length).forEach(n -> {
+ if (args[n].isAnnotationPresent(ExtractedValue.class)) {
+ if (void.class.equals(args[n].getAnnotation(ExtractedValue.class).type())) {
+ log.warning(String.format("Ignoring non-default %s type specified for %s by %s",
+ ExtractedValue.class.getSimpleName(), containerType, extractorType));
+ }
+ result.get().add(new ContainerElementKey(containerType, Integer.valueOf(n)));
+ }
+ });
+ });
+ });
+ return result.optional().filter(s -> s.size() == 1)
+ .orElseThrow(() -> new ValueExtractorDefinitionException(extractorType.getName())).iterator().next();
+ }
+
+ private static Integer validTypeArgumentIndex(Integer typeArgumentIndex, Class<?> containerClass) {
+ if (typeArgumentIndex != null) {
+ final int i = typeArgumentIndex.intValue();
+ Validate.isTrue(i >= 0 && i < containerClass.getTypeParameters().length,
+ "type argument index %d is invalid for container type %s", typeArgumentIndex, containerClass);
+ }
+ return typeArgumentIndex;
+ }
+
+ private final Integer typeArgumentIndex;
+ private final Class<?> containerClass;
+ private final LazyInt hashCode = new LazyInt(() -> Objects.hash(getContainerClass(), getTypeArgumentIndex()));
+ private final Lazy<String> toString = new Lazy<>(() -> String.format("%s: %s<[%d]>",
+ ContainerElementKey.class.getSimpleName(), getContainerClass().getName(), getTypeArgumentIndex()));
+ private final AnnotatedType annotatedType;
+
+ public ContainerElementKey(AnnotatedType containerType, Integer typeArgumentIndex) {
+ super();
+ Validate.notNull(containerType, "containerType");
+ this.containerClass = TypeUtils.getRawType(containerType.getType(), null);
+ this.typeArgumentIndex = validTypeArgumentIndex(typeArgumentIndex, containerClass);
+ this.annotatedType = typeArgumentIndex == null ? containerType : ((AnnotatedParameterizedType) containerType)
+ .getAnnotatedActualTypeArguments()[typeArgumentIndex.intValue()];
+ }
+
+ public Class<?> getContainerClass() {
+ return containerClass;
+ }
+
+ public Integer getTypeArgumentIndex() {
+ return typeArgumentIndex;
+ }
+
+ public AnnotatedType getAnnotatedType() {
+ return annotatedType;
+ }
+
+ @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)
+ .isPresent();
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode.getAsInt();
+ }
+
+ @Override
+ public String toString() {
+ return toString.get();
+ }
+
+ @Override
+ public int compareTo(ContainerElementKey o) {
+ return Comparator.comparing(ContainerElementKey::containerClassName)
+ .thenComparing(Comparator.comparing(ContainerElementKey::getTypeArgumentIndex)).compare(this, o);
+ }
+
+ public Set<ContainerElementKey> getAssignableKeys() {
+ final Lazy<Set<ContainerElementKey>> result = new Lazy<>(LinkedHashSet::new);
+
+ if (typeArgumentIndex != null) {
+ final TypeVariable<?> var = containerClass.getTypeParameters()[typeArgumentIndex.intValue()];
+
+ Stream
+ .concat(Stream.of(containerClass.getAnnotatedSuperclass()),
+ Stream.of(containerClass.getAnnotatedInterfaces()))
+ .filter(AnnotatedParameterizedType.class::isInstance).map(AnnotatedParameterizedType.class::cast)
+ .forEach(t -> {
+ final AnnotatedType[] args = t.getAnnotatedActualTypeArguments();
+
+ for (int i = 0; i < args.length; i++) {
+ if (args[i].getType().equals(var)) {
+ result.get().add(new ContainerElementKey(t, Integer.valueOf(i)));
+ }
+ }
+ });
+ }
+ return result.optional().map(Collections::unmodifiableSet).orElseGet(Collections::emptySet);
+ }
+
+ public boolean represents(TypeVariable<?> var) {
+ return Optional.ofNullable(typeArgumentIndex)
+ .map(index -> getContainerClass().getTypeParameters()[index.intValue()]).filter(var::equals).isPresent();
+ }
+
+ private String containerClassName() {
+ return getContainerClass().getName();
+ }
+}