BVAL-174
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java
index 9fb5c98..e0d7746 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java
@@ -1,128 +1,128 @@
-/*
- * 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.descriptor;
-
-import java.lang.annotation.ElementType;
-import java.lang.reflect.AnnotatedElement;
-import java.lang.reflect.Type;
-import java.lang.reflect.TypeVariable;
-import java.util.Map;
-import java.util.Set;
-
-import javax.validation.metadata.ConstraintDescriptor;
-import javax.validation.metadata.ElementDescriptor;
-
-import org.apache.bval.jsr.groups.GroupStrategy;
-import org.apache.bval.jsr.groups.GroupsComputer;
-import org.apache.bval.jsr.metadata.Meta;
-import org.apache.bval.util.Validate;
-import org.apache.bval.util.reflection.TypeUtils;
-
-public abstract class ElementD<E extends AnnotatedElement, R extends MetadataReader.ForElement<E, ?>>
- implements ElementDescriptor {
-
- public static abstract class NonRoot<P extends ElementD<?, ?>, E extends AnnotatedElement, R extends MetadataReader.ForElement<E, ?>>
- extends ElementD<E, R> {
-
- protected final P parent;
-
- protected NonRoot(R reader, P parent) {
- super(reader);
- this.parent = Validate.notNull(parent, "parent");
- }
-
- public P getParent() {
- return parent;
- }
-
- @Override
- public final Type getGenericType() {
- if (TypeUtils.containsTypeVariables(genericType)) {
- final Map<TypeVariable<?>, Type> args =
- TypeUtils.getTypeArguments(parent.getGenericType(), Object.class);
- return TypeUtils.unrollVariables(args, genericType);
- }
- return genericType;
- }
-
- @Override
- final protected BeanD<?> getBean() {
- return parent.getBean();
- }
-
- @Override
- public final GroupStrategy getGroupStrategy() {
- return getBean().getGroupStrategy();
- }
- }
-
- protected final Type genericType;
- final GroupsComputer groupsComputer;
-
- private final Meta<E> meta;
- private final Set<ConstraintD<?>> constraints;
-
- protected ElementD(R reader) {
- super();
- Validate.notNull(reader, "reader");
- this.meta = reader.meta;
- this.genericType = reader.meta.getType();
- this.constraints = reader.getConstraints();
- this.groupsComputer = reader.getValidatorFactory().getGroupsComputer();
- }
-
- @Override
- public final boolean hasConstraints() {
- return !constraints.isEmpty();
- }
-
- @SuppressWarnings({ "unchecked", "rawtypes" })
- @Override
- public final Set<ConstraintDescriptor<?>> getConstraintDescriptors() {
- return (Set) constraints;
- }
-
- @Override
- public final ConstraintFinder findConstraints() {
- return new Finder(groupsComputer, this);
- }
-
- public final ElementType getElementType() {
- return meta.getElementType();
- }
-
- public final E getTarget() {
- return meta.getHost();
- }
-
- public final Class<?> getDeclaringClass() {
- return meta.getDeclaringClass();
- }
-
- public abstract Type getGenericType();
-
- public abstract GroupStrategy getGroupStrategy();
-
- @Override
- public String toString() {
- return String.format("%s: %s", getClass().getSimpleName(), meta.describeHost());
- }
-
- protected abstract BeanD<?> getBean();
-}
+/*
+ * 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.descriptor;
+
+import java.lang.annotation.ElementType;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.Map;
+import java.util.Set;
+
+import javax.validation.metadata.ConstraintDescriptor;
+import javax.validation.metadata.ElementDescriptor;
+
+import org.apache.bval.jsr.groups.GroupStrategy;
+import org.apache.bval.jsr.groups.GroupsComputer;
+import org.apache.bval.jsr.metadata.Meta;
+import org.apache.bval.util.Validate;
+import org.apache.bval.util.reflection.TypeUtils;
+
+public abstract class ElementD<E extends AnnotatedElement, R extends MetadataReader.ForElement<E, ?>>
+ implements ElementDescriptor {
+
+ public static abstract class NonRoot<P extends ElementD<?, ?>, E extends AnnotatedElement, R extends MetadataReader.ForElement<E, ?>>
+ extends ElementD<E, R> {
+
+ protected final P parent;
+
+ protected NonRoot(R reader, P parent) {
+ super(reader);
+ this.parent = Validate.notNull(parent, "parent");
+ }
+
+ public P getParent() {
+ return parent;
+ }
+
+ @Override
+ public final Type getGenericType() {
+ if (TypeUtils.containsTypeVariables(genericType)) {
+ final Map<TypeVariable<?>, Type> args =
+ TypeUtils.getTypeArguments(parent.getGenericType(), Object.class);
+ return TypeUtils.unrollVariables(args, genericType);
+ }
+ return genericType;
+ }
+
+ @Override
+ final protected BeanD<?> getBean() {
+ return parent.getBean();
+ }
+
+ @Override
+ public final GroupStrategy getGroupStrategy() {
+ return getBean().getGroupStrategy();
+ }
+ }
+
+ protected final Type genericType;
+ final GroupsComputer groupsComputer;
+
+ private final Meta<E> meta;
+ private final Set<ConstraintD<?>> constraints;
+
+ protected ElementD(R reader) {
+ super();
+ Validate.notNull(reader, "reader");
+ this.meta = reader.meta;
+ this.genericType = reader.meta.getType();
+ this.constraints = reader.getConstraints();
+ this.groupsComputer = reader.getValidatorFactory().getGroupsComputer();
+ }
+
+ @Override
+ public boolean hasConstraints() {
+ return !constraints.isEmpty();
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ public Set<ConstraintDescriptor<?>> getConstraintDescriptors() {
+ return (Set) constraints;
+ }
+
+ @Override
+ public final ConstraintFinder findConstraints() {
+ return new Finder(groupsComputer, this);
+ }
+
+ public final ElementType getElementType() {
+ return meta.getElementType();
+ }
+
+ public final E getTarget() {
+ return meta.getHost();
+ }
+
+ public final Class<?> getDeclaringClass() {
+ return meta.getDeclaringClass();
+ }
+
+ public abstract Type getGenericType();
+
+ public abstract GroupStrategy getGroupStrategy();
+
+ @Override
+ public String toString() {
+ return String.format("%s: %s", getClass().getSimpleName(), meta.describeHost());
+ }
+
+ protected abstract BeanD<?> getBean();
+}
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ReturnValueD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ReturnValueD.java
index a2204fc..7be73f9 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ReturnValueD.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ReturnValueD.java
@@ -1,36 +1,98 @@
-/*
- * 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.descriptor;
-
-import java.lang.reflect.Executable;
-
-import javax.validation.metadata.ReturnValueDescriptor;
-
-public class ReturnValueD<P extends ExecutableD<?, ?, P>, E extends Executable> extends CascadableContainerD<P, E>
- implements ReturnValueDescriptor {
-
- ReturnValueD(MetadataReader.ForContainer<E> reader, P parent) {
- super(reader, parent);
- }
-
- @Override
- public Class<?> getElementClass() {
- return parent.getElementClass();
- }
-}
+/*
+ * 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.descriptor;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.util.HashSet;
+import java.util.Set;
+import javax.validation.ConstraintValidator;
+import javax.validation.UnexpectedTypeException;
+import javax.validation.metadata.ConstraintDescriptor;
+
+import javax.validation.metadata.ReturnValueDescriptor;
+import org.apache.bval.util.ValidatorUtils;
+import org.apache.bval.util.reflection.TypeUtils;
+
+public class ReturnValueD<P extends ExecutableD<?, ?, P>, E extends Executable> extends CascadableContainerD<P, E>
+ implements ReturnValueDescriptor {
+
+ private final Set<ConstraintD<?>> constraints;
+
+ ReturnValueD(MetadataReader.ForContainer<E> reader, P parent) {
+ super(reader, parent);
+ this.constraints = new HashSet<>(reader.getConstraints());
+
+ Class<?> validatedType;
+ if (reader.meta.getHost() instanceof Constructor)
+ {
+ validatedType = reader.meta.getDeclaringClass();
+ }
+ else
+ {
+ validatedType = ((Method) reader.meta.getHost()).getReturnType();
+ }
+
+ for (ConstraintDescriptor<?> c : constraints)
+ {
+ if (!hasValidatorForType(validatedType, c)
+ && (!c.getConstraintValidatorClasses().isEmpty() || !c.getComposingConstraints().isEmpty()))
+ {
+ String msg = "No validator found for (composition) constraint @"
+ + c.getAnnotation().annotationType().getSimpleName()
+ + " declared on \"" + reader.meta.getHost().toString()
+ + "\" for validated type \"" + validatedType.getName() + "\"";
+ throw new UnexpectedTypeException(msg);
+ }
+ }
+ }
+
+ private boolean hasValidatorForType(Class<?> validatedType, ConstraintDescriptor<?> c)
+ {
+ for (Class<? extends ConstraintValidator<?, ?>> validatorClass : c.getConstraintValidatorClasses())
+ {
+ if (TypeUtils.isAssignable(validatedType, ValidatorUtils.getValidatedType(validatorClass)))
+ {
+ return true;
+ }
+ }
+
+ for (ConstraintDescriptor<?> composite : c.getComposingConstraints())
+ {
+ if (hasValidatorForType(validatedType, composite))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean hasConstraints() {
+ return !constraints.isEmpty();
+ }
+
+ @SuppressWarnings({ "unchecked", "rawtypes" })
+ @Override
+ public Set<ConstraintDescriptor<?>> getConstraintDescriptors() {
+ return (Set) constraints;
+ }
+}
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ComputeConstraintValidatorClass.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ComputeConstraintValidatorClass.java
index 123754b..1336e2f 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ComputeConstraintValidatorClass.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ComputeConstraintValidatorClass.java
@@ -1,214 +1,191 @@
-/*
- * 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.annotation.Annotation;
-import java.lang.reflect.Array;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.lang.reflect.WildcardType;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import java.util.function.Function;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import javax.validation.ConstraintDefinitionException;
-import javax.validation.ConstraintValidator;
-import javax.validation.UnexpectedTypeException;
-import javax.validation.constraintvalidation.ValidationTarget;
-
-import org.apache.bval.jsr.ConstraintCached;
-import org.apache.bval.jsr.ConstraintCached.ConstraintValidatorInfo;
-import org.apache.bval.jsr.descriptor.ConstraintD;
-import org.apache.bval.util.Exceptions;
-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;
-import org.apache.commons.weaver.privilizer.Privilizing;
-import org.apache.commons.weaver.privilizer.Privilizing.CallTo;
-
-@Privilizing(@CallTo(Reflection.class))
-class ComputeConstraintValidatorClass<A extends Annotation>
- implements Supplier<Class<? extends ConstraintValidator<A, ?>>> {
-
- private static class TypeWrapper {
- final Class<?> componentType;
- final int arrayDepth;
-
- TypeWrapper(Class<?> type) {
- Class<?> c = type;
- int d = 0;
- while (Object[].class.isAssignableFrom(c)) {
- d++;
- c = c.getComponentType();
- }
- this.componentType = c;
- this.arrayDepth = d;
- }
-
- Class<?> unwrapArrayComponentType(Class<?> t) {
- Exceptions.raiseUnless(t.isAssignableFrom(componentType), IllegalArgumentException::new,
- "%s not assignable from %s", t, componentType);
- if (arrayDepth == 0) {
- return t;
- }
- return Array.newInstance(t, new int[arrayDepth]).getClass();
- }
- }
-
- private static final String CV = ConstraintValidator.class.getSimpleName();
- private static final WildcardType UNBOUNDED = TypeUtils.wildcardType().build();
-
- private static Class<?> getValidatedType(Class<? extends ConstraintValidator<?, ?>> validatorType) {
- final Type result = TypeUtils.getTypeArguments(validatorType, ConstraintValidator.class)
- .get(ConstraintValidator.class.getTypeParameters()[1]);
- if (!isSupported(result)) {
- Exceptions.raise(ConstraintDefinitionException::new, "Validated type %s declared by %s %s is unsupported",
- result, CV, validatorType.getName());
- }
- return TypeUtils.getRawType(result, null);
- }
-
- private static boolean isSupported(Type validatedType) {
- if (validatedType instanceof Class<?>) {
- return true;
- }
- if (validatedType instanceof ParameterizedType) {
- return Stream.of(((ParameterizedType) validatedType).getActualTypeArguments())
- .allMatch(arg -> TypeUtils.equals(arg, UNBOUNDED));
- }
- return false;
- }
-
- private final ConstraintCached constraintsCache;
- private final ConstraintD<?> descriptor;
- private final ValidationTarget validationTarget;
- private final Class<?> validatedType;
-
- ComputeConstraintValidatorClass(ConstraintCached constraintsCache, ConstraintD<A> descriptor,
- ValidationTarget validationTarget, Class<?> validatedType) {
- super();
- this.constraintsCache = Validate.notNull(constraintsCache, "constraintsCache");
- this.descriptor = Validate.notNull(descriptor, "descriptor");
- this.validationTarget = Validate.notNull(validationTarget, "validationTarget");
- this.validatedType = Validate.notNull(validatedType, "validatedType");
- }
-
- @Override
- public Class<? extends ConstraintValidator<A, ?>> get() {
- @SuppressWarnings("unchecked")
- final Class<A> constraintType = (Class<A>) descriptor.getAnnotation().annotationType();
- return findValidator(constraintsCache.getConstraintValidatorInfo(constraintType));
- }
-
- private Class<? extends ConstraintValidator<A, ?>> findValidator(Set<ConstraintValidatorInfo<A>> infos) {
- switch (validationTarget) {
- case PARAMETERS:
- return findCrossParameterValidator(infos);
- case ANNOTATED_ELEMENT:
- return findAnnotatedElementValidator(infos);
- default:
- return null;
- }
- }
-
- private Class<? extends ConstraintValidator<A, ?>> findCrossParameterValidator(
- Set<ConstraintValidatorInfo<A>> infos) {
-
- final Set<ConstraintValidatorInfo<A>> set =
- infos.stream().filter(info -> info.getSupportedTargets().contains(ValidationTarget.PARAMETERS))
- .collect(Collectors.toSet());
-
- @SuppressWarnings("unchecked")
- final Class<A> constraintType = (Class<A>) descriptor.getAnnotation().annotationType();
-
- final int size = set.size();
- Exceptions.raiseIf(size > 1 || !isComposed() && set.isEmpty(), ConstraintDefinitionException::new,
- "%d cross-parameter %ss found for constraint type %s", size, CV, constraintType);
-
- final Class<? extends ConstraintValidator<A, ?>> result = set.iterator().next().getType();
- if (!TypeUtils.isAssignable(Object[].class, getValidatedType(result))) {
- Exceptions.raise(ConstraintDefinitionException::new,
- "Cross-parameter %s %s does not support the validation of an object array", CV, result.getName());
- }
- return result;
- }
-
- private Class<? extends ConstraintValidator<A, ?>> findAnnotatedElementValidator(
- Set<ConstraintValidatorInfo<A>> infos) {
-
- final Map<Class<?>, Class<? extends ConstraintValidator<?, ?>>> validators = infos.stream()
- .filter(info -> info.getSupportedTargets().contains(ValidationTarget.ANNOTATED_ELEMENT))
- .map(ConstraintValidatorInfo::getType).collect(
- Collectors.toMap(ComputeConstraintValidatorClass::getValidatedType, Function.identity(), (v1, v2) -> {
- Exceptions.raiseUnless(Objects.equals(v1, v2), UnexpectedTypeException::new,
- "Detected collision of constraint and target type between %s and %s", v1, v2);
- return v1;
- }));
-
- final Map<Type, Class<? extends ConstraintValidator<?, ?>>> candidates = new HashMap<>();
-
- walkHierarchy().filter(validators::containsKey).forEach(type -> {
- // if we haven't already found a candidate whose validated type
- // is a subtype of the current evaluated type, save:
- if (!candidates.keySet().stream().anyMatch(k -> TypeUtils.isAssignable(k, type))) {
- candidates.put(type, validators.get(type));
- }
- });
- final String cond;
- switch (candidates.size()) {
- case 1:
- @SuppressWarnings("unchecked")
- final Class<? extends ConstraintValidator<A, ?>> result =
- (Class<? extends ConstraintValidator<A, ?>>) candidates.values().iterator().next();
- return result;
- case 0:
- if (isComposed()) {
- return null;
- }
- cond = "No compliant";
- break;
- default:
- cond = "> 1 maximally specific";
- break;
- }
- throw Exceptions.create(UnexpectedTypeException::new, "%s %s %s found for annotated element of type %s", cond,
- descriptor.getAnnotation().annotationType().getName(), CV, TypeUtils.toString(validatedType));
- }
-
- // account for validated array types by unwrapping and rewrapping component
- // type hierarchy:
- private Stream<Class<?>> walkHierarchy() {
- final TypeWrapper w = new TypeWrapper(Reflection.primitiveToWrapper(validatedType));
- Stream.Builder<Class<?>> hierarchy = Stream.builder();
- Reflection.hierarchy(w.componentType, Interfaces.INCLUDE).forEach(hierarchy);
- final Stream<Class<?>> result = hierarchy.build().map(w::unwrapArrayComponentType);
- if (validatedType.isInterface() || validatedType.isArray()) {
- return Stream.concat(result, Stream.of(Object.class));
- }
- return result;
- }
-
- private boolean isComposed() {
- return !descriptor.getComposingConstraints().isEmpty();
- }
-}
+/*
+ * 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.annotation.Annotation;
+import java.lang.reflect.Array;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.validation.ConstraintDefinitionException;
+import javax.validation.ConstraintValidator;
+import javax.validation.UnexpectedTypeException;
+import javax.validation.constraintvalidation.ValidationTarget;
+
+import org.apache.bval.jsr.ConstraintCached;
+import org.apache.bval.jsr.ConstraintCached.ConstraintValidatorInfo;
+import org.apache.bval.jsr.descriptor.ConstraintD;
+import org.apache.bval.util.Exceptions;
+import org.apache.bval.util.Validate;
+import org.apache.bval.util.ValidatorUtils;
+import org.apache.bval.util.reflection.Reflection;
+import org.apache.bval.util.reflection.Reflection.Interfaces;
+import org.apache.bval.util.reflection.TypeUtils;
+import org.apache.commons.weaver.privilizer.Privilizing;
+import org.apache.commons.weaver.privilizer.Privilizing.CallTo;
+
+@Privilizing(@CallTo(Reflection.class))
+class ComputeConstraintValidatorClass<A extends Annotation>
+ implements Supplier<Class<? extends ConstraintValidator<A, ?>>> {
+
+ private static class TypeWrapper {
+ final Class<?> componentType;
+ final int arrayDepth;
+
+ TypeWrapper(Class<?> type) {
+ Class<?> c = type;
+ int d = 0;
+ while (Object[].class.isAssignableFrom(c)) {
+ d++;
+ c = c.getComponentType();
+ }
+ this.componentType = c;
+ this.arrayDepth = d;
+ }
+
+ Class<?> unwrapArrayComponentType(Class<?> t) {
+ Exceptions.raiseUnless(t.isAssignableFrom(componentType), IllegalArgumentException::new,
+ "%s not assignable from %s", t, componentType);
+ if (arrayDepth == 0) {
+ return t;
+ }
+ return Array.newInstance(t, new int[arrayDepth]).getClass();
+ }
+ }
+
+ private static final String CV = ConstraintValidator.class.getSimpleName();
+
+ private final ConstraintCached constraintsCache;
+ private final ConstraintD<?> descriptor;
+ private final ValidationTarget validationTarget;
+ private final Class<?> validatedType;
+
+ ComputeConstraintValidatorClass(ConstraintCached constraintsCache, ConstraintD<A> descriptor,
+ ValidationTarget validationTarget, Class<?> validatedType) {
+ super();
+ this.constraintsCache = Validate.notNull(constraintsCache, "constraintsCache");
+ this.descriptor = Validate.notNull(descriptor, "descriptor");
+ this.validationTarget = Validate.notNull(validationTarget, "validationTarget");
+ this.validatedType = Validate.notNull(validatedType, "validatedType");
+ }
+
+ @Override
+ public Class<? extends ConstraintValidator<A, ?>> get() {
+ @SuppressWarnings("unchecked")
+ final Class<A> constraintType = (Class<A>) descriptor.getAnnotation().annotationType();
+ return findValidator(constraintsCache.getConstraintValidatorInfo(constraintType));
+ }
+
+ private Class<? extends ConstraintValidator<A, ?>> findValidator(Set<ConstraintValidatorInfo<A>> infos) {
+ switch (validationTarget) {
+ case PARAMETERS:
+ return findCrossParameterValidator(infos);
+ case ANNOTATED_ELEMENT:
+ return findAnnotatedElementValidator(infos);
+ default:
+ return null;
+ }
+ }
+
+ private Class<? extends ConstraintValidator<A, ?>> findCrossParameterValidator(
+ Set<ConstraintValidatorInfo<A>> infos) {
+
+ final Set<ConstraintValidatorInfo<A>> set =
+ infos.stream().filter(info -> info.getSupportedTargets().contains(ValidationTarget.PARAMETERS))
+ .collect(Collectors.toSet());
+
+ @SuppressWarnings("unchecked")
+ final Class<A> constraintType = (Class<A>) descriptor.getAnnotation().annotationType();
+
+ final int size = set.size();
+ Exceptions.raiseIf(size > 1 || !isComposed() && set.isEmpty(), ConstraintDefinitionException::new,
+ "%d cross-parameter %ss found for constraint type %s", size, CV, constraintType);
+
+ final Class<? extends ConstraintValidator<A, ?>> result = set.iterator().next().getType();
+ if (!TypeUtils.isAssignable(Object[].class, ValidatorUtils.getValidatedType(result))) {
+ Exceptions.raise(ConstraintDefinitionException::new,
+ "Cross-parameter %s %s does not support the validation of an object array", CV, result.getName());
+ }
+ return result;
+ }
+
+ private Class<? extends ConstraintValidator<A, ?>> findAnnotatedElementValidator(
+ Set<ConstraintValidatorInfo<A>> infos) {
+
+ final Map<Class<?>, Class<? extends ConstraintValidator<?, ?>>> validators = infos.stream()
+ .filter(info -> info.getSupportedTargets().contains(ValidationTarget.ANNOTATED_ELEMENT))
+ .map(ConstraintValidatorInfo::getType).collect(
+ Collectors.toMap(ValidatorUtils::getValidatedType, Function.identity(), (v1, v2) -> {
+ Exceptions.raiseUnless(Objects.equals(v1, v2), UnexpectedTypeException::new,
+ "Detected collision of constraint and target type between %s and %s", v1, v2);
+ return v1;
+ }));
+
+ final Map<Type, Class<? extends ConstraintValidator<?, ?>>> candidates = new HashMap<>();
+
+ walkHierarchy().filter(validators::containsKey).forEach(type -> {
+ // if we haven't already found a candidate whose validated type
+ // is a subtype of the current evaluated type, save:
+ if (!candidates.keySet().stream().anyMatch(k -> TypeUtils.isAssignable(k, type))) {
+ candidates.put(type, validators.get(type));
+ }
+ });
+ final String cond;
+ switch (candidates.size()) {
+ case 1:
+ @SuppressWarnings("unchecked")
+ final Class<? extends ConstraintValidator<A, ?>> result =
+ (Class<? extends ConstraintValidator<A, ?>>) candidates.values().iterator().next();
+ return result;
+ case 0:
+ if (isComposed()) {
+ return null;
+ }
+ cond = "No compliant";
+ break;
+ default:
+ cond = "> 1 maximally specific";
+ break;
+ }
+ throw Exceptions.create(UnexpectedTypeException::new, "%s %s %s found for annotated element of type %s", cond,
+ descriptor.getAnnotation().annotationType().getName(), CV, TypeUtils.toString(validatedType));
+ }
+
+ // account for validated array types by unwrapping and rewrapping component
+ // type hierarchy:
+ private Stream<Class<?>> walkHierarchy() {
+ final TypeWrapper w = new TypeWrapper(Reflection.primitiveToWrapper(validatedType));
+ Stream.Builder<Class<?>> hierarchy = Stream.builder();
+ Reflection.hierarchy(w.componentType, Interfaces.INCLUDE).forEach(hierarchy);
+ final Stream<Class<?>> result = hierarchy.build().map(w::unwrapArrayComponentType);
+ if (validatedType.isInterface() || validatedType.isArray()) {
+ return Stream.concat(result, Stream.of(Object.class));
+ }
+ return result;
+ }
+
+ private boolean isComposed() {
+ return !descriptor.getComposingConstraints().isEmpty();
+ }
+}
diff --git a/bval-jsr/src/main/java/org/apache/bval/util/ValidatorUtils.java b/bval-jsr/src/main/java/org/apache/bval/util/ValidatorUtils.java
new file mode 100644
index 0000000..11dad25
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/util/ValidatorUtils.java
@@ -0,0 +1,52 @@
+/*
+ * 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.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.WildcardType;
+import java.util.stream.Stream;
+import javax.validation.ConstraintDefinitionException;
+import javax.validation.ConstraintValidator;
+import org.apache.bval.util.reflection.TypeUtils;
+
+public class ValidatorUtils {
+
+ private static final WildcardType UNBOUNDED = TypeUtils.wildcardType().build();
+ private static final String CV = ConstraintValidator.class.getSimpleName();
+
+ public static Class<?> getValidatedType(Class<? extends ConstraintValidator<?, ?>> validatorType) {
+ final Type result = TypeUtils.getTypeArguments(validatorType, ConstraintValidator.class)
+ .get(ConstraintValidator.class.getTypeParameters()[1]);
+ if (!isSupported(result)) {
+ Exceptions.raise(ConstraintDefinitionException::new, "Validated type %s declared by %s %s is unsupported",
+ result, CV, validatorType.getName());
+ }
+ return TypeUtils.getRawType(result, null);
+ }
+
+ private static boolean isSupported(Type validatedType) {
+ if (validatedType instanceof Class<?>) {
+ return true;
+ }
+ if (validatedType instanceof ParameterizedType) {
+ return Stream.of(((ParameterizedType) validatedType).getActualTypeArguments())
+ .allMatch(arg -> TypeUtils.equals(arg, UNBOUNDED));
+ }
+ return false;
+ }
+}
diff --git a/bval-jsr/src/test/java/org/apache/bval/jsr/issues/BVAL174.java b/bval-jsr/src/test/java/org/apache/bval/jsr/issues/BVAL174.java
new file mode 100644
index 0000000..b55ce04
--- /dev/null
+++ b/bval-jsr/src/test/java/org/apache/bval/jsr/issues/BVAL174.java
@@ -0,0 +1,80 @@
+/*
+ * 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.issues;
+
+import java.lang.annotation.Documented;
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.METHOD;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import java.lang.annotation.Target;
+import java.util.Set;
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import javax.validation.Payload;
+
+public class BVAL174 {
+
+ @Audience("movies")
+ public String getMovie() {
+ return "";
+ }
+
+ @Audience("movies")
+ public void addMovie(String newMovie) {
+
+ }
+
+ @Documented
+ @javax.validation.Constraint(validatedBy = {Audience.Constraint.class})
+ @Target({METHOD, ANNOTATION_TYPE})
+ @Retention(RUNTIME)
+ public @interface Audience {
+
+ String value();
+
+ Class<?>[] groups() default {};
+
+ String message() default "The 'aud' claim must contain '{value}'";
+
+ Class<? extends Payload>[] payload() default {};
+
+ class Constraint implements ConstraintValidator<Audience, JsonWebToken> {
+ private Audience audience;
+
+ @Override
+ public void initialize(final Audience constraint) {
+ this.audience = constraint;
+ }
+
+ @Override
+ public boolean isValid(final JsonWebToken value, final ConstraintValidatorContext context) {
+ final Set<String> audience = value.getAudience();
+ return audience != null && audience.contains(this.audience.value());
+ }
+ }
+ }
+
+ public class JsonWebToken {
+
+ public Set<String> getAudience() {
+ return null;
+ }
+ }
+}
diff --git a/bval-jsr/src/test/java/org/apache/bval/jsr/issues/BVAL174Test.java b/bval-jsr/src/test/java/org/apache/bval/jsr/issues/BVAL174Test.java
new file mode 100644
index 0000000..92ebead
--- /dev/null
+++ b/bval-jsr/src/test/java/org/apache/bval/jsr/issues/BVAL174Test.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.bval.jsr.issues;
+
+import java.lang.reflect.Method;
+import javax.validation.UnexpectedTypeException;
+import javax.validation.Validation;
+import javax.validation.Validator;
+import javax.validation.metadata.MethodDescriptor;
+import org.apache.bval.jsr.ApacheValidationProvider;
+import static org.junit.Assert.assertTrue;
+import org.junit.Test;
+
+public class BVAL174Test {
+
+ private Validator getValidator() {
+ return Validation.byProvider(ApacheValidationProvider.class).configure().buildValidatorFactory().getValidator();
+ }
+
+ @Test(expected = UnexpectedTypeException.class)
+ public void testValidateReturnValue() throws NoSuchMethodException {
+ Validator validator = getValidator();
+
+ BVAL174 service = new BVAL174();
+ Method getMovie = service.getClass().getMethod("getMovie");
+ Method addMovie = service.getClass().getMethod("addMovie", String.class);
+
+ MethodDescriptor getMovieConstraints = validator.getConstraintsForClass(service.getClass())
+ .getConstraintsForMethod(getMovie.getName(), getMovie.getParameterTypes());
+
+ assertTrue(getMovieConstraints == null);
+
+ MethodDescriptor addMovieConstraints = validator.getConstraintsForClass(service.getClass())
+ .getConstraintsForMethod(addMovie.getName(), addMovie.getParameterTypes());
+
+ assertTrue(addMovieConstraints == null);
+ }
+
+}