BV2: new metadata model

Project: http://git-wip-us.apache.org/repos/asf/bval/repo
Commit: http://git-wip-us.apache.org/repos/asf/bval/commit/59bd964b
Tree: http://git-wip-us.apache.org/repos/asf/bval/tree/59bd964b
Diff: http://git-wip-us.apache.org/repos/asf/bval/diff/59bd964b

Branch: refs/heads/bv2
Commit: 59bd964bb20dc465c42123315c8d0f6e760bb728
Parents: 40fc20f
Author: Matt Benson <[email protected]>
Authored: Wed Feb 21 14:50:31 2018 -0600
Committer: Matt Benson <[email protected]>
Committed: Wed Feb 21 14:59:57 2018 -0600

----------------------------------------------------------------------
 .../bval/jsr/metadata/AnnotationBehavior.java   |  35 +
 .../AnnotationBehaviorMergeStrategy.java        |  54 ++
 ...otationDeclaredValidatorMappingProvider.java |  42 ++
 .../ClassLoadingValidatorMappingProvider.java   |  48 ++
 .../bval/jsr/metadata/CompositeBuilder.java     | 227 ++++++
 .../CompositeValidatorMappingProvider.java      |  42 ++
 .../bval/jsr/metadata/ContainerElementKey.java  | 175 +++++
 .../apache/bval/jsr/metadata/DualBuilder.java   | 243 +++++++
 .../metadata/DualValidationMappingProvider.java |  50 ++
 .../apache/bval/jsr/metadata/EmptyBuilder.java  | 183 +++++
 .../jsr/metadata/HasAnnotationBehavior.java     |  24 +
 .../bval/jsr/metadata/HierarchyBuilder.java     | 235 +++++++
 .../bval/jsr/metadata/MetadataBuilder.java      |  98 +++
 .../bval/jsr/metadata/MetadataBuilders.java     |  41 ++
 .../org/apache/bval/jsr/metadata/Metas.java     | 324 +++++++++
 .../bval/jsr/metadata/ReflectionBuilder.java    | 272 ++++++++
 .../org/apache/bval/jsr/metadata/Signature.java |  75 ++
 .../bval/jsr/metadata/ValidatorMapping.java     | 121 ++++
 .../jsr/metadata/ValidatorMappingProvider.java  |  51 ++
 .../apache/bval/jsr/metadata/XmlBuilder.java    | 694 +++++++++++++++++++
 .../metadata/XmlValidationMappingProvider.java  |  64 ++
 21 files changed, 3098 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/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/59bd964b/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/59bd964b/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/59bd964b/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/59bd964b/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/59bd964b/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/59bd964b/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();
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/DualBuilder.java
----------------------------------------------------------------------
diff --git 
a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/DualBuilder.java 
b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/DualBuilder.java
new file mode 100644
index 0000000..269d953
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/DualBuilder.java
@@ -0,0 +1,243 @@
+/*
+ *  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.Set;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import org.apache.bval.jsr.descriptor.GroupConversion;
+import org.apache.bval.jsr.util.ToUnmodifiable;
+import org.apache.bval.util.Validate;
+
+/**
+ * Maintains two metadata builds in parallel. The "primary" build is assumed 
to be the reflection/annotation-based build
+ * and is subject to the {@link AnnotationBehavior} prescribed by the "custom" 
build.
+ */
+public class DualBuilder {
+
+    private static class Delegator<DELEGATE extends HasAnnotationBehavior> 
implements HasAnnotationBehavior {
+
+        private final Delegator<?> parent;
+        protected final DELEGATE primaryDelegate;
+        protected final DELEGATE customDelegate;
+
+        Delegator(Delegator<?> parent, DELEGATE primaryDelegate, DELEGATE 
customDelegate) {
+            this.parent = parent;
+            this.primaryDelegate = Validate.notNull(primaryDelegate, 
"primaryDelegate");
+            this.customDelegate = Validate.notNull(customDelegate, 
"customDelegate");
+        }
+
+        AnnotationBehavior getCustomAnnotationBehavior() {
+            final AnnotationBehavior annotationBehavior = 
customDelegate.getAnnotationBehavior();
+            Validate.validState(annotationBehavior != null, "null %s returned 
from %s",
+                AnnotationBehavior.class.getSimpleName(), customDelegate);
+            if (annotationBehavior == AnnotationBehavior.ABSTAIN && parent != 
null) {
+                return parent.getCustomAnnotationBehavior();
+            }
+            return annotationBehavior;
+        }
+
+        protected Stream<DELEGATE> activeDelegates() {
+            return getCustomAnnotationBehavior() == AnnotationBehavior.EXCLUDE 
? Stream.of(customDelegate)
+                : Stream.of(primaryDelegate, customDelegate);
+        }
+
+        <K, D> Map<K, D> merge(Function<DELEGATE, Map<K, D>> toMap, 
BiFunction<D, D, D> parallel,
+            Supplier<D> emptyBuilder) {
+
+            final Map<K, D> primaries = toMap.apply(primaryDelegate);
+            final Map<K, D> customs = toMap.apply(customDelegate);
+
+            if (primaries.isEmpty() && customs.isEmpty()) {
+                return Collections.emptyMap();
+            }
+
+            final Function<? super K, ? extends D> valueMapper = k -> {
+                final D primary = primaries.get(k);
+                final D custom = customs.get(k);
+
+                if (custom == null) {
+                    if (primary != null) {
+                        switch (getCustomAnnotationBehavior()) {
+                        case INCLUDE:
+                        case ABSTAIN:
+                            return primary;
+                        default:
+                            break;
+                        }
+                    }
+                    return emptyBuilder.get();
+                }
+                return parallel.apply(primary, custom);
+            };
+            return Stream.of(primaries, 
customs).map(Map::keySet).flatMap(Collection::stream).distinct()
+                .collect(Collectors.toMap(Function.identity(), valueMapper));
+        }
+    }
+
+    private static class ForBean extends 
DualBuilder.Delegator<MetadataBuilder.ForBean>
+        implements MetadataBuilder.ForBean {
+
+        ForBean(MetadataBuilder.ForBean primaryDelegate, 
MetadataBuilder.ForBean customDelegate) {
+            super(null, primaryDelegate, customDelegate);
+        }
+
+        @Override
+        public MetadataBuilder.ForClass getClass(Metas<Class<?>> meta) {
+            return new DualBuilder.ForClass(this, 
primaryDelegate.getClass(meta), customDelegate.getClass(meta));
+        }
+
+        @Override
+        public Map<String, MetadataBuilder.ForContainer<Field>> 
getFields(Metas<Class<?>> meta) {
+            return merge(b -> b.getFields(meta), (t, u) -> new 
DualBuilder.ForContainer<>(this, t, u),
+                EmptyBuilder.instance()::forContainer);
+        }
+
+        @Override
+        public Map<String, MetadataBuilder.ForContainer<Method>> 
getGetters(Metas<Class<?>> meta) {
+            return merge(b -> b.getGetters(meta), (t, u) -> new 
DualBuilder.ForContainer<>(this, t, u),
+                EmptyBuilder.instance()::forContainer);
+        }
+
+        @Override
+        public Map<Signature, MetadataBuilder.ForExecutable<Constructor<?>>> 
getConstructors(Metas<Class<?>> meta) {
+            return merge(b -> b.getConstructors(meta), (t, u) -> new 
DualBuilder.ForExecutable<>(this, t, u),
+                EmptyBuilder.instance()::forExecutable);
+        }
+
+        @Override
+        public Map<Signature, MetadataBuilder.ForExecutable<Method>> 
getMethods(Metas<Class<?>> meta) {
+            return merge(b -> b.getMethods(meta), (t, u) -> new 
DualBuilder.ForExecutable<>(this, t, u),
+                EmptyBuilder.instance()::forExecutable);
+        }
+    }
+
+    private static class ForElement<DELEGATE extends 
MetadataBuilder.ForElement<E>, E extends AnnotatedElement>
+        extends Delegator<DELEGATE> implements MetadataBuilder.ForElement<E> {
+
+        ForElement(Delegator<?> parent, DELEGATE primaryDelegate, DELEGATE 
customDelegate) {
+            super(parent, primaryDelegate, customDelegate);
+        }
+
+        @Override
+        public final Annotation[] getDeclaredConstraints(Metas<E> meta) {
+            return activeDelegates().map(d -> 
d.getDeclaredConstraints(meta)).flatMap(Stream::of)
+                .toArray(Annotation[]::new);
+        }
+    }
+
+    private static class ForClass extends ForElement<MetadataBuilder.ForClass, 
Class<?>>
+        implements MetadataBuilder.ForClass {
+
+        ForClass(Delegator<?> parent, MetadataBuilder.ForClass primaryDelegate,
+            MetadataBuilder.ForClass customDelegate) {
+            super(parent, primaryDelegate, customDelegate);
+        }
+
+        @Override
+        public List<Class<?>> getGroupSequence(Metas<Class<?>> meta) {
+            final List<Class<?>> customGroupSequence = 
customDelegate.getGroupSequence(meta);
+            if (customGroupSequence != null) {
+                return customGroupSequence;
+            }
+            return customDelegate.getAnnotationBehavior() == 
AnnotationBehavior.EXCLUDE ? null
+                : primaryDelegate.getGroupSequence(meta);
+        }
+    }
+
+    private static class ForContainer<DELEGATE extends 
MetadataBuilder.ForContainer<E>, E extends AnnotatedElement>
+        extends DualBuilder.ForElement<DELEGATE, E> implements 
MetadataBuilder.ForContainer<E> {
+
+        ForContainer(Delegator<?> parent, DELEGATE primaryDelegate, DELEGATE 
customDelegate) {
+            super(parent, primaryDelegate, customDelegate);
+        }
+
+        @Override
+        public final boolean isCascade(Metas<E> meta) {
+            return activeDelegates().anyMatch(d -> d.isCascade(meta));
+        }
+
+        @Override
+        public final Set<GroupConversion> getGroupConversions(Metas<E> meta) {
+            return activeDelegates().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), (t, u) -> new 
DualBuilder.ForContainer<>(this, t, u),
+                EmptyBuilder.instance()::forContainer);
+        }
+    }
+
+    private static class ForExecutable<DELEGATE extends 
MetadataBuilder.ForExecutable<E>, E extends Executable>
+        extends Delegator<DELEGATE> implements 
MetadataBuilder.ForExecutable<E> {
+
+        ForExecutable(Delegator<?> parent, DELEGATE primaryDelegate, DELEGATE 
customDelegate) {
+            super(parent, primaryDelegate, customDelegate);
+        }
+
+        @Override
+        public MetadataBuilder.ForContainer<E> getReturnValue(Metas<E> meta) {
+            return new DualBuilder.ForContainer<>(this, 
primaryDelegate.getReturnValue(meta),
+                customDelegate.getReturnValue(meta));
+        }
+
+        @Override
+        public List<MetadataBuilder.ForContainer<Parameter>> 
getParameters(Metas<E> meta) {
+
+            final List<MetadataBuilder.ForContainer<Parameter>> primaries = 
primaryDelegate.getParameters(meta);
+            final List<MetadataBuilder.ForContainer<Parameter>> customs = 
customDelegate.getParameters(meta);
+
+            Validate.validState(primaries.size() == customs.size(), 
"Mismatched parameter counts: %d vs. %d",
+                primaries.size(), customs.size());
+
+            return IntStream.range(0, primaries.size())
+                .mapToObj(n -> new DualBuilder.ForContainer<>(this, 
primaries.get(n), customs.get(n)))
+                .collect(ToUnmodifiable.list());
+        }
+
+        @Override
+        public MetadataBuilder.ForElement<E> getCrossParameter(Metas<E> meta) {
+            return new DualBuilder.ForElement<MetadataBuilder.ForElement<E>, 
E>(this,
+                primaryDelegate.getCrossParameter(meta), 
customDelegate.getCrossParameter(meta));
+        }
+    }
+
+    public static MetadataBuilder.ForBean forBean(MetadataBuilder.ForBean 
primaryDelegate,
+        MetadataBuilder.ForBean customDelegate) {
+        return new DualBuilder.ForBean(primaryDelegate, customDelegate);
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/DualValidationMappingProvider.java
----------------------------------------------------------------------
diff --git 
a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/DualValidationMappingProvider.java
 
b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/DualValidationMappingProvider.java
new file mode 100644
index 0000000..e5b5038
--- /dev/null
+++ 
b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/DualValidationMappingProvider.java
@@ -0,0 +1,50 @@
+/*
+ *  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 org.apache.bval.util.Validate;
+
+public class DualValidationMappingProvider extends ValidatorMappingProvider {
+    private final ValidatorMappingProvider primaryDelegate;
+    private final ValidatorMappingProvider secondaryDelegate;
+
+    public DualValidationMappingProvider(ValidatorMappingProvider primary, 
ValidatorMappingProvider secondary) {
+        super();
+        this.primaryDelegate = Validate.notNull(primary, "primary delegate");
+        this.secondaryDelegate = Validate.notNull(secondary, "secondary 
delegate");
+    }
+
+    @Override
+    protected <A extends Annotation> ValidatorMapping<A> 
doGetValidatorMapping(Class<A> constraintType) {
+
+        final ValidatorMapping<A> secondaryMapping = 
secondaryDelegate.doGetValidatorMapping(constraintType);
+        if (secondaryMapping == null) {
+            return primaryDelegate.doGetValidatorMapping(constraintType);
+        }
+        final AnnotationBehavior annotationBehavior = 
secondaryMapping.getAnnotationBehavior();
+
+        if (annotationBehavior == AnnotationBehavior.EXCLUDE) {
+            return secondaryMapping;
+        }
+        return ValidatorMapping.merge(
+            
Arrays.asList(primaryDelegate.doGetValidatorMapping(constraintType), 
secondaryMapping),
+            AnnotationBehaviorMergeStrategy.consensus());
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/EmptyBuilder.java
----------------------------------------------------------------------
diff --git 
a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/EmptyBuilder.java 
b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/EmptyBuilder.java
new file mode 100644
index 0000000..c95f6d7
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/EmptyBuilder.java
@@ -0,0 +1,183 @@
+/*
+ *  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.Collections;
+import java.util.EnumMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.bval.jsr.descriptor.GroupConversion;
+import org.apache.bval.util.Lazy;
+import org.apache.bval.util.ObjectUtils;
+import org.apache.bval.util.Validate;
+
+public class EmptyBuilder {
+    private static final Map<AnnotationBehavior, EmptyBuilder> INSTANCES = new 
EnumMap<>(AnnotationBehavior.class);
+
+    public static EmptyBuilder instance() {
+        return instance(AnnotationBehavior.ABSTAIN);
+    }
+
+    public static EmptyBuilder instance(AnnotationBehavior annotationBehavior) 
{
+        return INSTANCES.computeIfAbsent(annotationBehavior, 
EmptyBuilder::new);
+    }
+
+    private class Level implements HasAnnotationBehavior {
+
+        @Override
+        public final AnnotationBehavior getAnnotationBehavior() {
+            return annotationBehavior;
+        }
+    }
+
+    private class ForBean extends Level implements MetadataBuilder.ForBean {
+        private final Lazy<EmptyBuilder.ForClass> forClass = new 
Lazy<>(EmptyBuilder.ForClass::new);
+
+        @Override
+        public MetadataBuilder.ForClass getClass(Metas<Class<?>> meta) {
+            return forClass.get();
+        }
+
+        @Override
+        public Map<String, MetadataBuilder.ForContainer<Field>> 
getFields(Metas<Class<?>> meta) {
+            return Collections.emptyMap();
+        }
+
+        @Override
+        public Map<String, MetadataBuilder.ForContainer<Method>> 
getGetters(Metas<Class<?>> meta) {
+            return Collections.emptyMap();
+        }
+
+        @Override
+        public Map<Signature, MetadataBuilder.ForExecutable<Constructor<?>>> 
getConstructors(Metas<Class<?>> meta) {
+            return Collections.emptyMap();
+        }
+
+        @Override
+        public Map<Signature, MetadataBuilder.ForExecutable<Method>> 
getMethods(Metas<Class<?>> meta) {
+            return Collections.emptyMap();
+        }
+
+        @Override
+        public boolean isEmpty() {
+            return true;
+        }
+    }
+
+    private class ForElement<E extends AnnotatedElement> extends Level 
implements MetadataBuilder.ForElement<E> {
+
+        @Override
+        public final Annotation[] getDeclaredConstraints(Metas<E> meta) {
+            return ObjectUtils.EMPTY_ANNOTATION_ARRAY;
+        }
+    }
+
+    private class ForClass extends ForElement<Class<?>> implements 
MetadataBuilder.ForClass {
+
+        @Override
+        public List<Class<?>> getGroupSequence(Metas<Class<?>> meta) {
+            return null;
+        }
+    }
+
+    private class ForContainer<E extends AnnotatedElement> extends 
ForElement<E>
+        implements MetadataBuilder.ForContainer<E> {
+
+        @Override
+        public boolean isCascade(Metas<E> meta) {
+            return false;
+        }
+
+        @Override
+        public Set<GroupConversion> getGroupConversions(Metas<E> meta) {
+            return Collections.emptySet();
+        }
+
+        @Override
+        public Map<ContainerElementKey, 
MetadataBuilder.ForContainer<AnnotatedType>> getContainerElementTypes(
+            Metas<E> meta) {
+            return Collections.emptyMap();
+        }
+    }
+
+    private class ForExecutable<E extends Executable> extends Level implements 
MetadataBuilder.ForExecutable<E> {
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public MetadataBuilder.ForElement<E> getCrossParameter(Metas<E> meta) {
+            return forElement.get();
+        }
+
+        @Override
+        public List<MetadataBuilder.ForContainer<Parameter>> 
getParameters(Metas<E> meta) {
+            return Collections.emptyList();
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public MetadataBuilder.ForContainer<E> getReturnValue(Metas<E> meta) {
+            return forContainer.get();
+        }
+    }
+
+    private final AnnotationBehavior annotationBehavior;
+    private final Lazy<EmptyBuilder.ForBean> forBean;
+    @SuppressWarnings("rawtypes")
+    private final Lazy<EmptyBuilder.ForContainer> forContainer;
+    @SuppressWarnings("rawtypes")
+    private final Lazy<EmptyBuilder.ForExecutable> forExecutable;
+    @SuppressWarnings("rawtypes")
+    private final Lazy<EmptyBuilder.ForElement> forElement;
+
+    private EmptyBuilder(AnnotationBehavior annotationBehavior) {
+        super();
+        this.annotationBehavior = Validate.notNull(annotationBehavior, 
"annotationBehavior");
+        forBean = new Lazy<>(EmptyBuilder.ForBean::new);
+        forContainer = new Lazy<>(EmptyBuilder.ForContainer::new);
+        forExecutable = new Lazy<>(EmptyBuilder.ForExecutable::new);
+        forElement = new Lazy<>(EmptyBuilder.ForElement::new);
+    }
+
+    public MetadataBuilder.ForBean forBean() {
+        return forBean.get();
+    }
+
+    @SuppressWarnings("unchecked")
+    public <E extends AnnotatedElement> MetadataBuilder.ForContainer<E> 
forContainer() {
+        return forContainer.get();
+    }
+
+    @SuppressWarnings("unchecked")
+    public <E extends Executable> MetadataBuilder.ForExecutable<E> 
forExecutable() {
+        return forExecutable.get();
+    }
+
+    @SuppressWarnings("unchecked")
+    public <E extends AnnotatedElement> MetadataBuilder.ForElement<E> 
forElement() {
+        return forElement.get();
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/HasAnnotationBehavior.java
----------------------------------------------------------------------
diff --git 
a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/HasAnnotationBehavior.java
 
b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/HasAnnotationBehavior.java
new file mode 100644
index 0000000..2060954
--- /dev/null
+++ 
b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/HasAnnotationBehavior.java
@@ -0,0 +1,24 @@
+/*
+ *  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;
+
+public interface HasAnnotationBehavior {
+
+    default AnnotationBehavior getAnnotationBehavior() {
+        return AnnotationBehavior.ABSTAIN;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/HierarchyBuilder.java
----------------------------------------------------------------------
diff --git 
a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/HierarchyBuilder.java 
b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/HierarchyBuilder.java
new file mode 100644
index 0000000..35276ea
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/HierarchyBuilder.java
@@ -0,0 +1,235 @@
+/*
+ *  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.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import javax.validation.metadata.Scope;
+
+import org.apache.bval.jsr.descriptor.GroupConversion;
+import org.apache.bval.util.Validate;
+import org.apache.bval.util.reflection.Reflection;
+import org.apache.bval.util.reflection.Reflection.Interfaces;
+import org.apache.commons.weaver.privilizer.Privilizing;
+import org.apache.commons.weaver.privilizer.Privilizing.CallTo;
+
+@Privilizing(@CallTo(Reflection.class))
+public class HierarchyBuilder extends CompositeBuilder {
+    private static abstract class HierarchyDelegate<T> {
+        final T delegate;
+
+        HierarchyDelegate(T delegate) {
+            super();
+            this.delegate = Validate.notNull(delegate, "delegate");
+        }
+
+        static class ForBean extends 
HierarchyDelegate<MetadataBuilder.ForBean> implements MetadataBuilder.ForBean {
+            final Metas<Class<?>> hierarchyType;
+
+            ForBean(MetadataBuilder.ForBean delegate, Class<?> hierarchyType) {
+                super(delegate);
+                this.hierarchyType = new Metas.ForClass(hierarchyType);
+            }
+
+            @Override
+            public MetadataBuilder.ForClass getClass(Metas<Class<?>> meta) {
+                return new 
HierarchyDelegate.ForClass(delegate.getClass(hierarchyType), hierarchyType);
+            }
+
+            @Override
+            public Map<String, MetadataBuilder.ForContainer<Field>> 
getFields(Metas<Class<?>> meta) {
+                return delegate.getFields(hierarchyType);
+            }
+
+            @Override
+            public Map<String, MetadataBuilder.ForContainer<Method>> 
getGetters(Metas<Class<?>> meta) {
+                return delegate.getGetters(hierarchyType);
+            }
+
+            @Override
+            public Map<Signature, 
MetadataBuilder.ForExecutable<Constructor<?>>> getConstructors(Metas<Class<?>> 
meta) {
+                return delegate.getConstructors(hierarchyType);
+            }
+
+            @Override
+            public Map<Signature, MetadataBuilder.ForExecutable<Method>> 
getMethods(Metas<Class<?>> meta) {
+                final Map<Signature, MetadataBuilder.ForExecutable<Method>> m 
= delegate.getMethods(hierarchyType);
+
+                return m;
+            }
+        }
+
+        static class ForClass extends 
HierarchyDelegate<MetadataBuilder.ForClass> implements MetadataBuilder.ForClass 
{
+
+            final Metas<Class<?>> hierarchyType;
+
+            ForClass(MetadataBuilder.ForClass delegate, Metas<Class<?>> 
hierarchyType) {
+                super(delegate);
+                this.hierarchyType = hierarchyType;
+            }
+
+            @Override
+            public Annotation[] getDeclaredConstraints(Metas<Class<?>> meta) {
+                return delegate.getDeclaredConstraints(hierarchyType);
+            }
+
+            @Override
+            public List<Class<?>> getGroupSequence(Metas<Class<?>> meta) {
+                return delegate.getGroupSequence(hierarchyType);
+            }
+        }
+
+        static class ForGetter extends 
HierarchyDelegate<MetadataBuilder.ForContainer<Method>>
+            implements MetadataBuilder.ForContainer<Method> {
+            final Metas.ForMethod meta;
+
+            ForGetter(MetadataBuilder.ForContainer<Method> delegate,
+                org.apache.bval.jsr.metadata.Metas.ForMethod meta) {
+                super(delegate);
+                this.meta = Validate.notNull(meta, "meta");
+            }
+
+            @Override
+            public Annotation[] getDeclaredConstraints(Metas<Method> meta) {
+                // TODO Auto-generated method stub
+                return null;
+            }
+
+            @Override
+            public boolean isCascade(Metas<Method> meta) {
+                // TODO Auto-generated method stub
+                return false;
+            }
+
+            @Override
+            public Set<GroupConversion> getGroupConversions(Metas<Method> 
meta) {
+                // TODO Auto-generated method stub
+                return null;
+            }
+
+            @Override
+            public Map<ContainerElementKey, 
org.apache.bval.jsr.metadata.MetadataBuilder.ForContainer<AnnotatedType>> 
getContainerElementTypes(
+                Metas<Method> meta) {
+                // TODO Auto-generated method stub
+                return null;
+            }
+        }
+
+        static class ForMethod extends 
HierarchyDelegate<MetadataBuilder.ForExecutable<Method>>
+            implements MetadataBuilder.ForExecutable<Method> {
+            final Metas.ForMethod meta;
+
+            public ForMethod(MetadataBuilder.ForExecutable<Method> delegate, 
Metas.ForMethod meta) {
+                super(delegate);
+                this.meta = Validate.notNull(meta, "meta");
+            }
+
+            @Override
+            public MetadataBuilder.ForContainer<Method> 
getReturnValue(Metas<Method> meta) {
+
+                // TODO Auto-generated method stub
+                return null;
+            }
+
+            @Override
+            public MetadataBuilder.ForElement<Method> 
getCrossParameter(Metas<Method> meta) {
+                // TODO Auto-generated method stub
+                return null;
+            }
+
+            @Override
+            public List<MetadataBuilder.ForContainer<Parameter>> 
getParameters(Metas<Method> meta) {
+                // TODO Auto-generated method stub
+                return null;
+            }
+        }
+    }
+
+    private final Function<Class<?>, MetadataBuilder.ForBean> getBeanBuilder;
+
+    public HierarchyBuilder(Function<Class<?>, MetadataBuilder.ForBean> 
getBeanBuilder) {
+        super(AnnotationBehaviorMergeStrategy.first());
+        this.getBeanBuilder = Validate.notNull(getBeanBuilder, "getBeanBuilder 
function was null");
+    }
+
+    public MetadataBuilder.ForBean forBean(Class<?> beanClass) {
+        final List<MetadataBuilder.ForBean> delegates = new ArrayList<>();
+
+        /*
+         * First add the delegate for the requested bean class, forcing to 
empty if absent. This is important for the
+         * same reason that we use the #first() 
AnnotationBehaviorMergeStrategy: namely, that custom metadata overrides
+         * only from the immediately available mapping per the BV spec.
+         */
+        delegates.add(Optional.of(beanClass).map(getBeanBuilder).orElseGet(() 
-> EmptyBuilder.instance().forBean()));
+
+        // iterate the hierarchy, skipping the first (i.e. beanClass handled
+        // above)
+        final Iterator<Class<?>> hierarchy = Reflection.hierarchy(beanClass, 
Interfaces.INCLUDE).iterator();
+        hierarchy.next();
+
+        // skip Object.class; skip null/empty hierarchy builders, mapping 
others
+        // to HierarchyDelegate
+        hierarchy
+            .forEachRemaining(t -> 
Optional.of(t).filter(Predicate.isEqual(Object.class).negate()).map(getBeanBuilder)
+                .filter(b -> !b.isEmpty()).map(b -> new 
HierarchyDelegate.ForBean(b, t)).ifPresent(delegates::add));
+
+        // if we have nothing but empty builders (which should only happen for
+        // absent custom metadata), return empty:
+        if (delegates.stream().allMatch(MetadataBuilder.ForBean::isEmpty)) {
+            return EmptyBuilder.instance().forBean();
+        }
+        return delegates.stream().collect(compose());
+    }
+
+    @Override
+    protected <E extends AnnotatedElement> Map<Scope, Annotation[]> 
getConstraintsByScope(
+        CompositeBuilder.ForElement<? extends MetadataBuilder.ForElement<E>, 
E> composite, Metas<E> meta) {
+
+        final Iterator<? extends MetadataBuilder.ForElement<E>> iter = 
composite.delegates.iterator();
+
+        final Map<Scope, Annotation[]> result = new EnumMap<>(Scope.class);
+        result.put(Scope.LOCAL_ELEMENT, 
iter.next().getDeclaredConstraints(meta));
+
+        if (iter.hasNext()) {
+            final List<Annotation> hierarchyConstraints = new ArrayList<>();
+            iter.forEachRemaining(d -> 
Collections.addAll(hierarchyConstraints, d.getDeclaredConstraints(meta)));
+            result.put(Scope.HIERARCHY, hierarchyConstraints.toArray(new 
Annotation[hierarchyConstraints.size()]));
+        }
+        return result;
+    }
+
+    @Override
+    protected List<Class<?>> getGroupSequence(CompositeBuilder.ForClass 
composite, Metas<Class<?>> meta) {
+        return composite.delegates.get(0).getGroupSequence(meta);
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataBuilder.java
----------------------------------------------------------------------
diff --git 
a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataBuilder.java 
b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataBuilder.java
new file mode 100644
index 0000000..7dbdcbc
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataBuilder.java
@@ -0,0 +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.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.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.validation.metadata.Scope;
+
+import org.apache.bval.jsr.descriptor.GroupConversion;
+
+/**
+ * Common interface for populating the Bean Validation descriptors from 
various sources. Most implementations should
+ * concern themselves with a single level of an inheritance hierarchy.
+ */
+public final class MetadataBuilder {
+
+    public interface ForBean extends HasAnnotationBehavior {
+        MetadataBuilder.ForClass getClass(Metas<Class<?>> meta);
+
+        Map<String, ForContainer<Field>> getFields(Metas<Class<?>> meta);
+
+        /**
+         * Returned keys are property names per XML mapping spec.
+         * 
+         * @param meta
+         * @return {@link Map}
+         */
+        Map<String, ForContainer<Method>> getGetters(Metas<Class<?>> meta);
+
+        Map<Signature, ForExecutable<Constructor<?>>> 
getConstructors(Metas<Class<?>> meta);
+
+        Map<Signature, ForExecutable<Method>> getMethods(Metas<Class<?>> meta);
+
+        default boolean isEmpty() {
+            return false;
+        }
+    }
+
+    public interface ForElement<E extends AnnotatedElement> extends 
HasAnnotationBehavior {
+
+        Annotation[] getDeclaredConstraints(Metas<E> meta);
+
+        default Map<Scope, Annotation[]> getConstraintsByScope(Metas<E> meta) {
+            return Collections.singletonMap(Scope.LOCAL_ELEMENT, 
getDeclaredConstraints(meta));
+        }
+    }
+
+    public interface ForClass extends ForElement<Class<?>> {
+
+        List<Class<?>> getGroupSequence(Metas<Class<?>> meta);
+    }
+
+    public interface ForContainer<E extends AnnotatedElement> extends 
MetadataBuilder.ForElement<E> {
+
+        boolean isCascade(Metas<E> meta);
+
+        Set<GroupConversion> getGroupConversions(Metas<E> meta);
+
+        Map<ContainerElementKey, ForContainer<AnnotatedType>> 
getContainerElementTypes(Metas<E> meta);
+    }
+
+    public interface ForExecutable<E extends Executable> extends 
HasAnnotationBehavior {
+
+        MetadataBuilder.ForContainer<E> getReturnValue(Metas<E> meta);
+
+        MetadataBuilder.ForElement<E> getCrossParameter(Metas<E> meta);
+
+        List<ForContainer<Parameter>> getParameters(Metas<E> meta);
+    }
+
+    private MetadataBuilder() {
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataBuilders.java
----------------------------------------------------------------------
diff --git 
a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataBuilders.java 
b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataBuilders.java
new file mode 100644
index 0000000..aa301a4
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/MetadataBuilders.java
@@ -0,0 +1,41 @@
+/*
+ *  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.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.bval.util.Validate;
+
+public class MetadataBuilders {
+
+    private final Map<Class<?>, List<MetadataBuilder.ForBean>> beanBuilders = 
new ConcurrentHashMap<>();
+
+    public <T> void registerCustomBuilder(Class<?> bean, 
MetadataBuilder.ForBean builder) {
+        Validate.notNull(bean, "bean");
+        Validate.notNull(builder, "builder");
+        beanBuilders.computeIfAbsent(bean, c -> new 
ArrayList<>()).add(builder);
+    }
+
+    public List<MetadataBuilder.ForBean> getCustomBuilders(Class<?> bean) {
+        final List<MetadataBuilder.ForBean> list = beanBuilders.get(bean);
+        return list == null ? Collections.emptyList() : 
Collections.unmodifiableList(list);
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/59bd964b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Metas.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Metas.java 
b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Metas.java
new file mode 100644
index 0000000..667c404
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/Metas.java
@@ -0,0 +1,324 @@
+/*
+ * 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.annotation.ElementType;
+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.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.lang.reflect.Type;
+import java.util.Objects;
+
+import javax.validation.constraintvalidation.ValidationTarget;
+
+import org.apache.bval.util.Validate;
+
+/**
+ * Validation class model.
+ *
+ * @param <E>
+ */
+// TODO rename to Meta; delete old type of that name
+public abstract class Metas<E extends AnnotatedElement> {
+
+    public static class ForClass extends Metas<Class<?>> {
+
+        public ForClass(Class<?> host) {
+            super(host, ElementType.TYPE);
+        }
+
+        @Override
+        public final Class<?> getDeclaringClass() {
+            return getHost();
+        }
+
+        @Override
+        public Type getType() {
+            return getHost();
+        }
+
+        @Override
+        public AnnotatedType getAnnotatedType() {
+            return new AnnotatedType() {
+
+                @Override
+                public Annotation[] getDeclaredAnnotations() {
+                    return getHost().getDeclaredAnnotations();
+                }
+
+                @Override
+                public Annotation[] getAnnotations() {
+                    return getHost().getAnnotations();
+                }
+
+                @Override
+                public <T extends Annotation> T getAnnotation(Class<T> 
annotationClass) {
+                    return getHost().getAnnotation(annotationClass);
+                }
+
+                @Override
+                public Type getType() {
+                    return getHost();
+                }
+            };
+        }
+
+        @Override
+        public String getName() {
+            return getHost().getName();
+        }
+    }
+
+    public static abstract class ForMember<M extends Member & 
AnnotatedElement> extends Metas<M> {
+
+        protected ForMember(M host, ElementType elementType) {
+            super(host, elementType);
+        }
+
+        @Override
+        public Class<?> getDeclaringClass() {
+            return getHost().getDeclaringClass();
+        }
+    }
+
+    public static class ForField extends ForMember<Field> {
+
+        public ForField(Field host) {
+            super(host, ElementType.FIELD);
+        }
+
+        @Override
+        public Type getType() {
+            return getHost().getGenericType();
+        }
+
+        @Override
+        public AnnotatedType getAnnotatedType() {
+            return getHost().getAnnotatedType();
+        }
+
+        @Override
+        public String getName() {
+            return getHost().getName();
+        }
+    }
+
+    public static abstract class ForExecutable<E extends Executable> extends 
ForMember<E> {
+
+        protected ForExecutable(E host, ElementType elementType) {
+            super(host, elementType);
+        }
+
+        @Override
+        public AnnotatedType getAnnotatedType() {
+            return getHost().getAnnotatedReturnType();
+        }
+    }
+
+    public static class ForConstructor extends ForExecutable<Constructor<?>> {
+
+        public ForConstructor(Constructor<?> host) {
+            super(host, ElementType.CONSTRUCTOR);
+        }
+
+        @Override
+        public Type getType() {
+            return getHost().getDeclaringClass();
+        }
+
+        @Override
+        public String getName() {
+            return getHost().getDeclaringClass().getSimpleName();
+        }
+    }
+
+    public static class ForMethod extends ForExecutable<Method> {
+
+        public ForMethod(Method host) {
+            super(host, ElementType.METHOD);
+        }
+
+        @Override
+        public Type getType() {
+            return getHost().getGenericReturnType();
+        }
+
+        @Override
+        public String getName() {
+            return getHost().getName();
+        }
+    }
+
+    public static class ForCrossParameter<E extends Executable> extends 
Metas.ForExecutable<E> {
+
+        public ForCrossParameter(Metas<E> parent) {
+            super(parent.getHost(), parent.getElementType());
+        }
+
+        @Override
+        public Type getType() {
+            return Object[].class;
+        }
+
+        @Override
+        public String getName() {
+            return "<cross parameter>";
+        }
+
+        @Override
+        public ValidationTarget getValidationTarget() {
+            return ValidationTarget.PARAMETERS;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("%s(%s of %s)", getStringPrefix(), getName(), 
getHost());
+        }
+    }
+
+    public static class ForParameter extends Metas<Parameter> {
+
+        private final String name;
+
+        public ForParameter(Parameter host, String name) {
+            super(host, ElementType.PARAMETER);
+            this.name = Validate.notNull(name, "name");
+        }
+
+        @Override
+        public Type getType() {
+            return getHost().getType();
+        }
+
+        @Override
+        public Class<?> getDeclaringClass() {
+            return getHost().getDeclaringExecutable().getDeclaringClass();
+        }
+
+        @Override
+        public AnnotatedType getAnnotatedType() {
+            return getHost().getAnnotatedType();
+        }
+
+        public String getName() {
+            return name;
+        }
+    }
+
+    public static class ForContainerElement extends Metas<AnnotatedType> {
+
+        private final Metas<?> parent;
+        private final ContainerElementKey key;
+
+        public ForContainerElement(Metas<?> parent, ContainerElementKey key) {
+            super(key.getAnnotatedType(), ElementType.TYPE_USE);
+            this.parent = Validate.notNull(parent, "parent");
+            this.key = Validate.notNull(key, "key");
+        }
+
+        @Override
+        public Type getType() {
+            return getHost().getType();
+        }
+
+        @Override
+        public Class<?> getDeclaringClass() {
+            return parent.getDeclaringClass();
+        }
+
+        @Override
+        public AnnotatedType getAnnotatedType() {
+            return key.getAnnotatedType();
+        }
+
+        public Integer getTypeArgumentIndex() {
+            return Integer.valueOf(key.getTypeArgumentIndex());
+        }
+
+        @Override
+        public String getName() {
+            return key.toString();
+        }
+
+        @Override
+        public String toString() {
+            return String.format("%s(%s of %s)", getStringPrefix(), key, 
getHost());
+        }
+    }
+
+    private final E host;
+    private final ElementType elementType;
+
+    protected Metas(E host, ElementType elementType) {
+        super();
+        this.host = Validate.notNull(host, "host");
+        this.elementType = Validate.notNull(elementType, "elementType");
+    }
+
+    public E getHost() {
+        return host;
+    }
+
+    public ElementType getElementType() {
+        return elementType;
+    }
+
+    public abstract Type getType();
+
+    public abstract Class<?> getDeclaringClass();
+
+    public abstract AnnotatedType getAnnotatedType();
+
+    public abstract String getName();
+
+    public ValidationTarget getValidationTarget() {
+        return ValidationTarget.ANNOTATED_ELEMENT;
+    }
+
+    @Override
+    public String toString() {
+        return String.format("%s(%s)", getStringPrefix(), host);
+    }
+
+    protected String getStringPrefix() {
+        return Metas.class.getSimpleName() + '.' + getClass().getSimpleName();
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (!obj.getClass().equals(getClass())) {
+            return false;
+        }
+        return Objects.equals(((Metas<?>) obj).getHost(), getHost());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getHost());
+    }
+}

Reply via email to