This is an automated email from the ASF dual-hosted git repository.

tandraschko pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/bval.git


The following commit(s) were added to refs/heads/master by this push:
     new e0fe2cd  BVAL-174
e0fe2cd is described below

commit e0fe2cd9754abd730086cf893407cc73c3690eac
Author: Thomas Andraschko <[email protected]>
AuthorDate: Fri Jun 7 14:03:33 2019 +0200

    BVAL-174
---
 .../org/apache/bval/jsr/descriptor/ElementD.java   | 256 ++++++-------
 .../apache/bval/jsr/descriptor/ReturnValueD.java   | 134 +++++--
 .../jsr/job/ComputeConstraintValidatorClass.java   | 405 ++++++++++-----------
 .../java/org/apache/bval/util/ValidatorUtils.java  |  52 +++
 .../java/org/apache/bval/jsr/issues/BVAL174.java   |  80 ++++
 .../org/apache/bval/jsr/issues/BVAL174Test.java    |  55 +++
 6 files changed, 604 insertions(+), 378 deletions(-)

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

Reply via email to