http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java
----------------------------------------------------------------------
diff --git 
a/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java 
b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java
new file mode 100644
index 0000000..3bc69b2
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/metadata/XmlBuilder.java
@@ -0,0 +1,715 @@
+/*
+ *  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.io.Serializable;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.AnnotatedParameterizedType;
+import java.lang.reflect.AnnotatedType;
+import java.lang.reflect.Array;
+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.math.BigDecimal;
+import java.util.Collections;
+import java.util.EnumSet;
+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 java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.validation.ConstraintDeclarationException;
+import javax.validation.ConstraintTarget;
+import javax.validation.Payload;
+import javax.validation.ValidationException;
+import javax.xml.bind.JAXBElement;
+
+import org.apache.bval.jsr.ConstraintAnnotationAttributes;
+import org.apache.bval.jsr.descriptor.GroupConversion;
+import org.apache.bval.jsr.util.AnnotationsManager;
+import org.apache.bval.jsr.util.Exceptions;
+import org.apache.bval.jsr.util.ToUnmodifiable;
+import org.apache.bval.jsr.xml.AnnotationProxyBuilder;
+import org.apache.bval.jsr.xml.AnnotationType;
+import org.apache.bval.jsr.xml.BeanType;
+import org.apache.bval.jsr.xml.ClassType;
+import org.apache.bval.jsr.xml.ConstraintMappingsType;
+import org.apache.bval.jsr.xml.ConstraintType;
+import org.apache.bval.jsr.xml.ConstructorType;
+import org.apache.bval.jsr.xml.ContainerElementTypeType;
+import org.apache.bval.jsr.xml.CrossParameterType;
+import org.apache.bval.jsr.xml.ElementType;
+import org.apache.bval.jsr.xml.FieldType;
+import org.apache.bval.jsr.xml.GetterType;
+import org.apache.bval.jsr.xml.GroupConversionType;
+import org.apache.bval.jsr.xml.GroupSequenceType;
+import org.apache.bval.jsr.xml.GroupsType;
+import org.apache.bval.jsr.xml.MethodType;
+import org.apache.bval.jsr.xml.ParameterType;
+import org.apache.bval.jsr.xml.PayloadType;
+import org.apache.bval.jsr.xml.ReturnValueType;
+import org.apache.bval.jsr.xml.ValidationMappingParser;
+import org.apache.bval.util.Lazy;
+import org.apache.bval.util.ObjectUtils;
+import org.apache.bval.util.Validate;
+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 class XmlBuilder {
+    //@formatter:off
+    public enum Version {
+        v10("1.0"), v11("1.1"), v20("2.0");
+
+        final BigDecimal number;
+        private final String id;
+
+        private Version(String number) {
+            this.id = number;
+            this.number = new BigDecimal(number);
+        }
+
+        public String getId() {
+            return id;
+        }
+    }
+    //@formatter:on
+
+    private class ForBean implements MetadataBuilder.ForBean {
+
+        private final BeanType descriptor;
+
+        ForBean(BeanType descriptor) {
+            super();
+            this.descriptor = Validate.notNull(descriptor, "descriptor");
+        }
+
+        Class<?> getBeanClass() {
+            return resolveClass(descriptor.getClazz());
+        }
+
+        @Override
+        public MetadataBuilder.ForClass getClass(Metas<Class<?>> meta) {
+            return new XmlBuilder.ForClass(descriptor.getClassType());
+        }
+
+        @Override
+        public Map<String, MetadataBuilder.ForContainer<Field>> 
getFields(Metas<Class<?>> meta) {
+            return descriptor.getField().stream()
+                .collect(ToUnmodifiable.map(FieldType::getName, 
XmlBuilder.ForField::new));
+        }
+
+        @Override
+        public Map<String, MetadataBuilder.ForContainer<Method>> 
getGetters(Metas<Class<?>> meta) {
+            return descriptor.getGetter().stream()
+                .collect(ToUnmodifiable.map(GetterType::getName, 
XmlBuilder.ForGetter::new));
+        }
+
+        @Override
+        public Map<Signature, MetadataBuilder.ForExecutable<Constructor<?>>> 
getConstructors(Metas<Class<?>> meta) {
+            if (!atLeast(Version.v11)) {
+                return Collections.emptyMap();
+            }
+            final Function<ConstructorType, Class<?>[]> params = ct -> 
ct.getParameter().stream()
+                
.map(ParameterType::getType).map(XmlBuilder.this::resolveClass).toArray(Class[]::new);
+
+            final Function<ConstructorType, Signature> signature =
+                ct -> new Signature(meta.getHost().getSimpleName(), 
params.apply(ct));
+
+            return descriptor.getConstructor().stream()
+                .collect(Collectors.toMap(signature, 
XmlBuilder.ForConstructor::new));
+        }
+
+        @Override
+        public Map<Signature, MetadataBuilder.ForExecutable<Method>> 
getMethods(Metas<Class<?>> meta) {
+            if (!atLeast(Version.v11)) {
+                return Collections.emptyMap();
+            }
+            final Function<MethodType, Class<?>[]> params = mt -> 
mt.getParameter().stream().map(ParameterType::getType)
+                .map(XmlBuilder.this::resolveClass).toArray(Class[]::new);
+
+            final Function<MethodType, Signature> signature = mt -> new 
Signature(mt.getName(), params.apply(mt));
+
+            return 
descriptor.getMethod().stream().collect(Collectors.toMap(signature, 
XmlBuilder.ForMethod::new));
+        }
+
+        @Override
+        public final AnnotationBehavior getAnnotationBehavior() {
+            return descriptor.getIgnoreAnnotations() ? 
AnnotationBehavior.EXCLUDE : AnnotationBehavior.INCLUDE;
+        }
+    }
+
+    private class NonRootLevel<SELF extends NonRootLevel<SELF, D>, D> 
implements MetadataBuilder.Level {
+        protected final D descriptor;
+        private Lazy<Boolean> getIgnoreAnnotations;
+
+        public NonRootLevel(D descriptor) {
+            super();
+            this.descriptor = Validate.notNull(descriptor, "descriptor");
+        }
+
+        @Override
+        public final AnnotationBehavior getAnnotationBehavior() {
+            return Optional.ofNullable(getIgnoreAnnotations).map(Lazy::get)
+                .map(b -> b.booleanValue() ? AnnotationBehavior.EXCLUDE : 
AnnotationBehavior.INCLUDE)
+                .orElse(AnnotationBehavior.ABSTAIN);
+        }
+
+        @SuppressWarnings("unchecked")
+        final SELF withGetIgnoreAnnotations(Function<D, Boolean> 
getIgnoreAnnotations) {
+            Validate.notNull(getIgnoreAnnotations);
+            this.getIgnoreAnnotations = new Lazy<>(() -> 
getIgnoreAnnotations.apply(descriptor));
+            return (SELF) this;
+        }
+    }
+
+    private class ForElement<SELF extends XmlBuilder.ForElement<SELF, E, D>, E 
extends AnnotatedElement, D>
+        extends NonRootLevel<SELF, D> implements MetadataBuilder.ForElement<E> 
{
+
+        private Lazy<Annotation[]> getDeclaredConstraints;
+
+        ForElement(D descriptor) {
+            super(descriptor);
+        }
+
+        @Override
+        public final Annotation[] getDeclaredConstraints(Metas<E> meta) {
+            return lazy(getDeclaredConstraints, "getDeclaredConstraints");
+        }
+
+        final SELF withGetConstraintTypes(Function<D, List<ConstraintType>> 
getConstraintTypes) {
+            return withGetDeclaredConstraints(getConstraintTypes
+                .andThen(l -> 
l.stream().map(XmlBuilder.this::createConstraint).toArray(Annotation[]::new)));
+        }
+
+        @SuppressWarnings("unchecked")
+        final SELF withGetDeclaredConstraints(Function<D, Annotation[]> 
getDeclaredConstraints) {
+            this.getDeclaredConstraints = new Lazy<>(() -> 
getDeclaredConstraints.apply(descriptor));
+            return (SELF) this;
+        }
+    }
+
+    private class ForClass extends ForElement<ForClass, Class<?>, ClassType> 
implements MetadataBuilder.ForClass {
+
+        ForClass(ClassType descriptor) {
+            super(descriptor);
+            this.withGetConstraintTypes(ct -> ct.getConstraint());
+        }
+
+        @Override
+        public List<Class<?>> getGroupSequence(Metas<Class<?>> meta) {
+            final GroupSequenceType groupSequence = 
descriptor.getGroupSequence();
+            return groupSequence == null ? null
+                : 
groupSequence.getValue().stream().map(XmlBuilder.this::resolveClass).collect(ToUnmodifiable.list());
+        }
+    }
+
+    private class ForContainer<SELF extends XmlBuilder.ForContainer<SELF, E, 
D>, E extends AnnotatedElement, D>
+        extends XmlBuilder.ForElement<SELF, E, D> implements 
MetadataBuilder.ForContainer<E> {
+
+        private Lazy<Boolean> isCascade;
+        private Lazy<Set<GroupConversion>> getGroupConversions;
+        private Lazy<List<ContainerElementTypeType>> getContainerElementTypes;
+
+        ForContainer(D descriptor) {
+            super(descriptor);
+        }
+
+        @Override
+        public boolean isCascade(Metas<E> meta) {
+            Validate.validState(isCascade != null, "isCascade not set");
+            return isCascade.get().booleanValue();
+        }
+
+        @Override
+        public Set<GroupConversion> getGroupConversions(Metas<E> meta) {
+            Validate.validState(getGroupConversions != null, 
"getGroupConversions not set");
+            return getGroupConversions.get();
+        }
+
+        @Override
+        public Map<ContainerElementKey, 
MetadataBuilder.ForContainer<AnnotatedType>> getContainerElementTypes(
+            Metas<E> meta) {
+
+            if (!atLeast(Version.v20)) {
+                return Collections.emptyMap();
+            }
+
+            Validate.validState(getContainerElementTypes != null, 
"getContainerElementTypes not set");
+
+            final List<ContainerElementTypeType> elements = 
lazy(getContainerElementTypes, "getContainerElementTypes");
+
+            final AnnotatedType annotatedType = meta.getAnnotatedType();
+
+            final E host = meta.getHost();
+
+            if (annotatedType instanceof AnnotatedParameterizedType) {
+                final AnnotatedType[] actualTypeArguments =
+                    ((AnnotatedParameterizedType) 
annotatedType).getAnnotatedActualTypeArguments();
+
+                return elements.stream().collect(ToUnmodifiable.map(cet -> {
+                    Integer typeArgumentIndex = cet.getTypeArgumentIndex();
+                    if (typeArgumentIndex == null) {
+                        if (actualTypeArguments.length > 1) {
+                            Exceptions.raise(ValidationException::new, 
"Missing required type argument index for %s",
+                                host);
+                        }
+                        typeArgumentIndex = Integer.valueOf(0);
+                    }
+                    return new 
ContainerElementKey((AnnotatedParameterizedType) annotatedType, 
typeArgumentIndex);
+                }, XmlBuilder.ForContainerElementType::new));
+            }
+            if (!elements.isEmpty()) {
+                Exceptions.raise(ValidationException::new, "Illegally 
specified %d container element type(s) for %s",
+                    elements.size(), host);
+            }
+            return Collections.emptyMap();
+        }
+
+        @SuppressWarnings("unchecked")
+        SELF withGetValid(Function<D, String> getValid) {
+            Validate.notNull(getValid);
+            this.isCascade = new Lazy<>(() -> getValid.apply(descriptor) != 
null);
+            return (SELF) this;
+        }
+
+        @SuppressWarnings("unchecked")
+        SELF withGetGroupConversions(Function<D, List<GroupConversionType>> 
getGroupConversions) {
+            Validate.notNull(getGroupConversions);
+
+            this.getGroupConversions = new Lazy<>(() -> {
+                return getGroupConversions.apply(descriptor).stream().map(gc 
-> {
+                    final Class<?> source = resolveClass(gc.getFrom());
+                    final Class<?> target = resolveClass(gc.getTo());
+                    return GroupConversion.from(source).to(target);
+                }).collect(ToUnmodifiable.set());
+            });
+
+            return (SELF) this;
+        }
+
+        @SuppressWarnings("unchecked")
+        SELF withGetContainerElementTypes(Function<D, 
List<ContainerElementTypeType>> getContainerElementTypes) {
+            Validate.notNull(getContainerElementTypes);
+            this.getContainerElementTypes = new Lazy<>(() -> 
getContainerElementTypes.apply(descriptor));
+            return (SELF) this;
+        }
+    }
+
+    private class ForContainerElementType
+        extends ForContainer<ForContainerElementType, AnnotatedType, 
ContainerElementTypeType> {
+
+        ForContainerElementType(ContainerElementTypeType descriptor) {
+            super(descriptor);
+            
this.withGetConstraintTypes(ContainerElementTypeType::getConstraint)
+                .withGetValid(ContainerElementTypeType::getValid)
+                
.withGetGroupConversions(ContainerElementTypeType::getConvertGroup)
+                
.withGetContainerElementTypes(ContainerElementTypeType::getContainerElementType);
+        }
+    }
+
+    private class ForField extends XmlBuilder.ForContainer<ForField, Field, 
FieldType> {
+
+        ForField(FieldType descriptor) {
+            super(descriptor);
+            this.withGetIgnoreAnnotations(FieldType::getIgnoreAnnotations)
+                
.withGetConstraintTypes(FieldType::getConstraint).withGetValid(FieldType::getValid)
+                .withGetGroupConversions(FieldType::getConvertGroup)
+                
.withGetContainerElementTypes(FieldType::getContainerElementType);
+        }
+    }
+
+    private class ForGetter extends XmlBuilder.ForContainer<ForGetter, Method, 
GetterType> {
+
+        ForGetter(GetterType descriptor) {
+            super(descriptor);
+            this.withGetIgnoreAnnotations(GetterType::getIgnoreAnnotations)
+                
.withGetConstraintTypes(GetterType::getConstraint).withGetValid(GetterType::getValid)
+                .withGetGroupConversions(GetterType::getConvertGroup)
+                
.withGetContainerElementTypes(GetterType::getContainerElementType);
+        }
+    }
+
+    private abstract class ForExecutable<SELF extends ForExecutable<SELF, E, 
D>, E extends Executable, D>
+        extends NonRootLevel<SELF, D> implements 
MetadataBuilder.ForExecutable<E> {
+
+        Lazy<ReturnValueType> getReturnValue;
+        Lazy<CrossParameterType> getCrossParameter;
+        Lazy<List<ParameterType>> getParameters;
+
+        ForExecutable(D descriptor) {
+            super(descriptor);
+
+        }
+
+        @Override
+        public MetadataBuilder.ForElement<E> getCrossParameter(Metas<E> meta) {
+            return new XmlBuilder.ForCrossParameter<>(lazy(getCrossParameter, 
"getCrossParameter"));
+        }
+
+        @Override
+        public MetadataBuilder.ForContainer<E> getReturnValue(Metas<E> meta) {
+            return new XmlBuilder.ForReturnValue<>(lazy(getReturnValue, 
"getReturnValue"));
+        }
+
+        @Override
+        public List<MetadataBuilder.ForContainer<Parameter>> 
getParameters(Metas<E> meta) {
+            return lazy(getParameters, 
"getParameters").stream().map(XmlBuilder.ForParameter::new)
+                .collect(Collectors.toList());
+        }
+
+        @SuppressWarnings("unchecked")
+        SELF withGetReturnValue(Function<D, ReturnValueType> getReturnValue) {
+            Validate.notNull(getReturnValue);
+            this.getReturnValue = new Lazy<>(() -> 
getReturnValue.apply(descriptor));
+            return (SELF) this;
+        }
+
+        @SuppressWarnings("unchecked")
+        SELF withGetCrossParameter(Function<D, CrossParameterType> 
getCrossParameter) {
+            Validate.notNull(getCrossParameter);
+            this.getCrossParameter = new Lazy<>(() -> 
getCrossParameter.apply(descriptor));
+            return (SELF) this;
+        }
+
+        @SuppressWarnings("unchecked")
+        SELF withGetParameters(Function<D, List<ParameterType>> getParameters) 
{
+            Validate.notNull(getParameters);
+            this.getParameters = new Lazy<>(() -> 
getParameters.apply(descriptor));
+            return (SELF) this;
+        }
+    }
+
+    private class ForConstructor extends ForExecutable<ForConstructor, 
Constructor<?>, ConstructorType> {
+
+        ForConstructor(ConstructorType descriptor) {
+            super(descriptor);
+            
this.withGetIgnoreAnnotations(ConstructorType::getIgnoreAnnotations)
+                .withGetReturnValue(ConstructorType::getReturnValue)
+                .withGetCrossParameter(ConstructorType::getCrossParameter)
+                .withGetParameters(ConstructorType::getParameter);
+        }
+    }
+
+    private class ForMethod extends ForExecutable<ForMethod, Method, 
MethodType> {
+
+        ForMethod(MethodType descriptor) {
+            super(descriptor);
+            this.withGetIgnoreAnnotations(MethodType::getIgnoreAnnotations)
+                
.withGetReturnValue(MethodType::getReturnValue).withGetCrossParameter(MethodType::getCrossParameter)
+                .withGetParameters(MethodType::getParameter);
+        }
+    }
+
+    private class ForParameter extends ForContainer<ForParameter, Parameter, 
ParameterType> {
+
+        ForParameter(ParameterType descriptor) {
+            super(descriptor);
+            this.withGetIgnoreAnnotations(ParameterType::getIgnoreAnnotations)
+                
.withGetConstraintTypes(ParameterType::getConstraint).withGetValid(ParameterType::getValid)
+                .withGetGroupConversions(ParameterType::getConvertGroup)
+                
.withGetContainerElementTypes(ParameterType::getContainerElementType);
+        }
+    }
+
+    private class ForCrossParameter<E extends Executable>
+        extends ForElement<ForCrossParameter<E>, E, CrossParameterType> {
+
+        ForCrossParameter(CrossParameterType descriptor) {
+            super(descriptor);
+            
this.withGetIgnoreAnnotations(CrossParameterType::getIgnoreAnnotations)
+                .withGetDeclaredConstraints(d -> d.getConstraint().stream()
+                    .map(ct -> createConstraint(ct, 
ConstraintTarget.PARAMETERS)).toArray(Annotation[]::new));
+        }
+    }
+
+    private class ForReturnValue<E extends Executable> extends 
ForContainer<ForReturnValue<E>, E, ReturnValueType> {
+
+        ForReturnValue(ReturnValueType descriptor) {
+            super(descriptor);
+            this.withGetDeclaredConstraints(d -> d.getConstraint().stream()
+                .map(ct -> createConstraint(ct, 
ConstraintTarget.RETURN_VALUE)).toArray(Annotation[]::new))
+                .withGetContainerElementTypes(d -> 
d.getContainerElementType());
+        }
+    }
+
+    private static final Set<ConstraintAnnotationAttributes> RESERVED_PARAMS = 
Collections
+        .unmodifiableSet(EnumSet.of(ConstraintAnnotationAttributes.GROUPS, 
ConstraintAnnotationAttributes.MESSAGE,
+            ConstraintAnnotationAttributes.PAYLOAD, 
ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO));
+
+    static final <T> T lazy(Lazy<T> lazy, String name) {
+        Validate.validState(lazy != null, "%s not set", name);
+        return lazy.get();
+    }
+
+    private final ConstraintMappingsType constraintMappings;
+    private final BigDecimal version;
+
+    public XmlBuilder(ConstraintMappingsType constraintMappings) {
+        super();
+        this.constraintMappings = constraintMappings;
+        Validate.notNull(constraintMappings, "constraintMappings");
+
+        BigDecimal v;
+        try {
+            v = new BigDecimal(constraintMappings.getVersion());
+        } catch (NumberFormatException e) {
+            v = Version.v10.number;
+        }
+        this.version = v;
+    }
+
+    public Map<Class<?>, MetadataBuilder.ForBean> forBeans() {
+        return 
constraintMappings.getBean().stream().map(XmlBuilder.ForBean::new)
+            .collect(ToUnmodifiable.map(XmlBuilder.ForBean::getBeanClass, 
Function.identity()));
+    }
+
+    public String getDefaultPackage() {
+        return constraintMappings.getDefaultPackage();
+    }
+
+    boolean atLeast(Version v) {
+        return version.compareTo(v.number) >= 0;
+    }
+
+    <T> Class<T> resolveClass(String className) {
+        return loadClass(toQualifiedClassName(className));
+    }
+
+    private String toQualifiedClassName(String className) {
+        if (isQualifiedClass(className)) {
+            return className;
+        }
+        if (className.startsWith("[L") && className.endsWith(";")) {
+            return "[L" + getDefaultPackage() + "." + className.substring(2);
+        }
+        return getDefaultPackage() + "." + className;
+    }
+
+    private boolean isQualifiedClass(String clazz) {
+        return clazz.indexOf('.') >= 0;
+    }
+
+    @SuppressWarnings("unchecked")
+    private <T> Class<T> loadClass(final String fqn) {
+        ClassLoader loader = Reflection.getClassLoader(XmlBuilder.class);
+        if (loader == null) {
+            loader = getClass().getClassLoader();
+        }
+        try {
+            return (Class<T>) Class.forName(fqn, true, loader);
+        } catch (ClassNotFoundException ex) {
+            throw Exceptions.create(ValidationException::new, ex, "Unable to 
load class: %d", fqn);
+        }
+    }
+
+    private Class<?>[] loadClasses(Supplier<Stream<String>> classNames) {
+        return streamClasses(classNames).toArray(Class[]::new);
+    }
+
+    private Stream<Class<?>> streamClasses(Supplier<Stream<String>> 
classNames) {
+        return classNames.get().map(c -> loadClass(c));
+    }
+
+    private <A extends Annotation, T> A createConstraint(final ConstraintType 
constraint) {
+        return createConstraint(constraint, ConstraintTarget.IMPLICIT);
+    }
+
+    @SuppressWarnings("unchecked")
+    private <A extends Annotation, T> A createConstraint(final ConstraintType 
constraint, ConstraintTarget target) {
+
+        final Class<A> annotationClass = (Class<A>) 
loadClass(toQualifiedClassName(constraint.getAnnotation()));
+        final AnnotationProxyBuilder<A> annoBuilder = new 
AnnotationProxyBuilder<A>(annotationClass);
+
+        if (constraint.getMessage() != null) {
+            annoBuilder.setMessage(constraint.getMessage());
+        }
+        annoBuilder.setGroups(getGroups(constraint.getGroups()));
+        annoBuilder.setPayload(getPayload(constraint.getPayload()));
+
+        if (AnnotationsManager.declaresAttribute(annotationClass,
+            
ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO.getAttributeName())) {
+            annoBuilder.setValidationAppliesTo(target);
+        }
+
+        for (final ElementType elementType : constraint.getElement()) {
+            final String name = elementType.getName();
+            checkValidName(name);
+
+            final Class<?> returnType = 
getAnnotationParameterType(annotationClass, name);
+            final Object elementValue = getElementValue(elementType, 
returnType);
+            annoBuilder.putValue(name, elementValue);
+        }
+        return annoBuilder.createAnnotation();
+    }
+
+    private void checkValidName(String name) {
+        
Exceptions.raiseIf(RESERVED_PARAMS.stream().map(ConstraintAnnotationAttributes::getAttributeName)
+            .anyMatch(Predicate.isEqual(name)), ValidationException::new, "%s 
is a reserved parameter name.", name);
+    }
+
+    private <A extends Annotation> Class<?> getAnnotationParameterType(final 
Class<A> annotationClass,
+        final String name) {
+        final Method m = Reflection.getPublicMethod(annotationClass, name);
+        Exceptions.raiseIf(m == null, ValidationException::new,
+            "Annotation of type %s does not contain a parameter %s.", 
annotationClass.getName(), name);
+        return m.getReturnType();
+    }
+
+    private Object getElementValue(ElementType elementType, Class<?> 
returnType) {
+        removeEmptyContentElements(elementType);
+
+        final List<Serializable> content = elementType.getContent();
+        final int sz = content.size();
+        if (returnType.isArray()) {
+            final Object result = 
Array.newInstance(returnType.getComponentType(), sz);
+            for (int i = 0; i < sz; i++) {
+                Array.set(result, i, getSingleValue(content.get(i), 
returnType.getComponentType()));
+            }
+            return result;
+        }
+        Exceptions.raiseIf(sz != 1, ValidationException::new,
+            "Attempt to specify an array where single value is expected.");
+
+        return getSingleValue(content.get(0), returnType);
+    }
+
+    private void removeEmptyContentElements(ElementType elementType) {
+        for (Iterator<Serializable> iter = 
elementType.getContent().iterator(); iter.hasNext();) {
+            final Serializable content = iter.next();
+            if (content instanceof String && ((String) content).matches("[\\n 
].*")) {
+                iter.remove();
+            }
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private Object getSingleValue(Serializable serializable, Class<?> 
returnType) {
+        if (serializable instanceof String) {
+            return convertToResultType(returnType, (String) serializable);
+        }
+        if (serializable instanceof JAXBElement<?>) {
+            final JAXBElement<?> elem = (JAXBElement<?>) serializable;
+            if (String.class.equals(elem.getDeclaredType())) {
+                return convertToResultType(returnType, (String) 
elem.getValue());
+            }
+            if (AnnotationType.class.equals(elem.getDeclaredType())) {
+                AnnotationType annotationType = (AnnotationType) 
elem.getValue();
+                try {
+                    return createAnnotation(annotationType, (Class<? extends 
Annotation>) returnType);
+                } catch (ClassCastException e) {
+                    throw new ValidationException("Unexpected parameter 
value");
+                }
+            }
+        }
+        throw new ValidationException("Unexpected parameter value");
+    }
+
+    private Object convertToResultType(Class<?> returnType, String value) {
+        /**
+         * Class is represented by the fully qualified class name of the 
class. spec: Note that if the raw string is
+         * unqualified, default package is taken into account.
+         */
+        if (String.class.equals(returnType)) {
+            return value;
+        }
+        if (Class.class.equals(returnType)) {
+            ClassLoader cl = 
Reflection.getClassLoader(ValidationMappingParser.class);
+            try {
+                return Reflection.toClass(toQualifiedClassName(value), cl);
+            } catch (Exception e) {
+                throw new ValidationException(e);
+            }
+        }
+        if (returnType.isEnum()) {
+            try {
+                @SuppressWarnings({ "rawtypes", "unchecked" })
+                final Enum e = Enum.valueOf(returnType.asSubclass(Enum.class), 
value);
+                return e;
+            } catch (IllegalArgumentException e) {
+                throw new ConstraintDeclarationException(e);
+            }
+        }
+        if (Byte.class.equals(returnType) || byte.class.equals(returnType)) { 
// spec mandates it
+            return Byte.parseByte(value);
+        }
+        if (Short.class.equals(returnType) || short.class.equals(returnType)) {
+            return Short.parseShort(value);
+        }
+        if (Integer.class.equals(returnType) || int.class.equals(returnType)) {
+            return Integer.parseInt(value);
+        }
+        if (Long.class.equals(returnType) || long.class.equals(returnType)) {
+            return Long.parseLong(value);
+        }
+        if (Float.class.equals(returnType) || float.class.equals(returnType)) {
+            return Float.parseFloat(value);
+        }
+        if (Double.class.equals(returnType) || 
double.class.equals(returnType)) {
+            return Double.parseDouble(value);
+        }
+        if (Boolean.class.equals(returnType) || 
boolean.class.equals(returnType)) {
+            return Boolean.parseBoolean(value);
+        }
+        if (Character.class.equals(returnType) || 
char.class.equals(returnType)) {
+            if (value.length() > 1) {
+                Exceptions.raise(ConstraintDeclarationException::new, "a char 
must have a length of 1");
+            }
+            return value.charAt(0);
+        }
+        throw new ValidationException(String.format("Unknown annotation value 
type %s", returnType.getName()));
+    }
+
+    private <A extends Annotation> Annotation createAnnotation(AnnotationType 
annotationType, Class<A> returnType) {
+        final AnnotationProxyBuilder<A> metaAnnotation = new 
AnnotationProxyBuilder<>(returnType);
+        for (ElementType elementType : annotationType.getElement()) {
+            final String name = elementType.getName();
+            metaAnnotation.putValue(name, getElementValue(elementType, 
getAnnotationParameterType(returnType, name)));
+        }
+        return metaAnnotation.createAnnotation();
+    }
+
+    private Class<?>[] getGroups(GroupsType groupsType) {
+        if (groupsType == null) {
+            return ObjectUtils.EMPTY_CLASS_ARRAY;
+        }
+        return loadClasses(groupsType.getValue()::stream);
+    }
+
+    @SuppressWarnings("unchecked")
+    private Class<? extends Payload>[] getPayload(PayloadType payloadType) {
+        if (payloadType == null) {
+            return (Class<? extends Payload>[]) ObjectUtils.EMPTY_CLASS_ARRAY;
+        }
+        return streamClasses(payloadType.getValue()::stream).peek(pc -> {
+            if (!Payload.class.isAssignableFrom(pc)) {
+                Exceptions.raise(ConstraintDeclarationException::new,
+                    "Specified payload class %s does not implement %s", 
pc.getName(), Payload.class.getName());
+            }
+        }).<Class<? extends Payload>> map(pc -> 
pc.asSubclass(Payload.class)).toArray(Class[]::new);
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationsManager.java
----------------------------------------------------------------------
diff --git 
a/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationsManager.java 
b/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationsManager.java
new file mode 100644
index 0000000..aac8ec1
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/AnnotationsManager.java
@@ -0,0 +1,279 @@
+/*
+ * 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.util;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Repeatable;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.validation.Constraint;
+import javax.validation.ConstraintDefinitionException;
+import javax.validation.OverridesAttribute;
+import javax.validation.ValidationException;
+import javax.validation.constraintvalidation.ValidationTarget;
+
+import org.apache.bval.jsr.ApacheValidatorFactory;
+import org.apache.bval.jsr.ConfigurationImpl;
+import org.apache.bval.jsr.ConstraintAnnotationAttributes;
+import org.apache.bval.jsr.ConstraintCached.ConstraintValidatorInfo;
+import org.apache.bval.jsr.xml.AnnotationProxyBuilder;
+import org.apache.bval.util.Lazy;
+import org.apache.bval.util.StringUtils;
+import org.apache.bval.util.Validate;
+import org.apache.bval.util.reflection.Reflection;
+import org.apache.commons.weaver.privilizer.Privilizing;
+import org.apache.commons.weaver.privilizer.Privilizing.CallTo;
+
+/**
+ * Manages (constraint) annotations according to the BV spec.
+ * 
+ * @since 2.0
+ */
+@Privilizing(@CallTo(Reflection.class))
+public class AnnotationsManager {
+    private static final class OverriddenAnnotationSpecifier {
+        final Class<? extends Annotation> annotationType;
+        final int constraintIndex;
+
+        OverriddenAnnotationSpecifier(OverridesAttribute annotation) {
+            this.annotationType = annotation.annotationType();
+            this.constraintIndex = annotation.constraintIndex();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (obj == this) {
+                return true;
+            }
+            if (obj == null || !obj.getClass().equals(getClass())) {
+                return false;
+            }
+            final OverriddenAnnotationSpecifier other = 
(OverriddenAnnotationSpecifier) obj;
+            return Objects.equals(annotationType, other.annotationType) && 
constraintIndex == other.constraintIndex;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(annotationType, constraintIndex);
+        }
+
+    }
+
+    private static class Composition {
+        final Lazy<Map<OverriddenAnnotationSpecifier, Map<String, String>>> 
overrides = new Lazy<>(HashMap::new);
+        final Annotation[] components;
+
+        Composition(Class<? extends Annotation> annotationType) {
+            components = Stream.of(annotationType.getDeclaredAnnotations())
+                .filter(a -> 
a.annotationType().isAnnotationPresent(Constraint.class)).toArray(Annotation[]::new);
+
+            if (!isComposed()) {
+                return;
+            }
+            for (Method m : Reflection.getDeclaredMethods(annotationType)) {
+                final String from = m.getName();
+                for (OverridesAttribute overridesAttribute : 
m.getDeclaredAnnotationsByType(OverridesAttribute.class)) {
+                    final String to =
+                        
Optional.of(overridesAttribute.name()).filter(StringUtils::isNotBlank).orElse(from);
+                    final Map<String, String> attributeMapping = 
overrides.get()
+                        .computeIfAbsent(new 
OverriddenAnnotationSpecifier(overridesAttribute), k -> new HashMap<>());
+                    if (attributeMapping.containsKey(to)) {
+                        throw new IllegalStateException(
+                            String.format("Attempt to override %s#%s() index 
%d from multiple sources",
+                                overridesAttribute.constraint(), to, 
overridesAttribute.constraintIndex()));
+                    }
+                    attributeMapping.put(to, from);
+                }
+            }
+        }
+
+        boolean isComposed() {
+            return components.length > 0;
+        }
+
+        Annotation[] getComponents(Annotation source) {
+            final Annotation[] result = components.clone();
+            if (overrides.optional().isPresent()) {
+                final Map<Class<? extends Annotation>, List<Annotation>> 
constraints = new HashMap<>();
+                for (Annotation constraint : result) {
+                    constraints.computeIfAbsent(constraint.annotationType(), k 
-> new ArrayList<>()).add(constraint);
+                }
+                final Map<String, Object> sourceAttributes = 
readAttributes(source);
+                overrides.get().forEach((spec, mappings) -> {
+                    final List<Annotation> ofType = 
constraints.get(spec.annotationType);
+                    final int actualIndex;
+                    if (spec.constraintIndex < 0) {
+                        Validate.validState(ofType.size() == 1, "Expected a 
single composing %s constraint",
+                            spec.annotationType);
+                        actualIndex = 0;
+                    } else {
+                        actualIndex = spec.constraintIndex;
+                    }
+                    final AnnotationProxyBuilder<Annotation> proxyBuilder =
+                        new AnnotationProxyBuilder<>(ofType.get(actualIndex));
+
+                    boolean changed = false;
+                    for (Map.Entry<String, String> e : mappings.entrySet()) {
+                        final Object value = 
sourceAttributes.get(e.getValue());
+                        changed = 
Objects.equals(proxyBuilder.putValue(e.getKey(), value), value) || changed;
+                    }
+                    if (changed) {
+                        ofType.set(actualIndex, 
proxyBuilder.createAnnotation());
+                    }
+                });
+                // now write the results back into the result array:
+                
constraints.values().stream().flatMap(Collection::stream).toArray(n -> result);
+            }
+            return result;
+        }
+    }
+
+    public static Map<String, Object> readAttributes(Annotation a) {
+        final Lazy<Map<String, Object>> result = new 
Lazy<>(LinkedHashMap::new);
+
+        Stream.of(Reflection.getDeclaredMethods(a.annotationType())).filter(m 
-> m.getParameterCount() == 0)
+            .forEach(m -> {
+                final boolean mustUnset = Reflection.setAccessible(m, true);
+                try {
+                    result.get().put(m.getName(), m.invoke(a));
+                } catch (IllegalAccessException | IllegalArgumentException | 
InvocationTargetException e) {
+                    throw new ValidationException("Caught exception reading 
attributes of " + a, e);
+                } finally {
+                    if (mustUnset) {
+                        Reflection.setAccessible(m, false);
+                    }
+                }
+            });
+        return 
result.optional().map(Collections::unmodifiableMap).orElseGet(Collections::emptyMap);
+    }
+
+    /**
+     * Meta-annotation aware.
+     * 
+     * @param e
+     * @param t
+     * @return {@code boolean}
+     * @see AnnotatedElement#isAnnotationPresent(Class)
+     */
+    public static boolean isAnnotationPresent(AnnotatedElement e, Class<? 
extends Annotation> t) {
+        if (e.isAnnotationPresent(t)) {
+            return true;
+        }
+        return 
Stream.of(e.getAnnotations()).map(Annotation::annotationType).anyMatch(a -> 
isAnnotationPresent(a, t));
+    }
+
+    /**
+     * Get declared annotations with a particular meta-annotation.
+     * 
+     * @param e
+     * @param meta
+     * @return {@link Annotation}[]
+     */
+    public static Annotation[] getDeclared(AnnotatedElement e, Class<? extends 
Annotation> meta) {
+        return Stream.of(e.getDeclaredAnnotations()).filter(ann -> 
isAnnotationPresent(ann.annotationType(), meta))
+            .toArray(Annotation[]::new);
+    }
+
+    /**
+     * Accounts for {@link Constraint} meta-annotation AND {@link Repeatable} 
constraint annotations.
+     * 
+     * @param e
+     * @return Annotation[]
+     */
+    public static Annotation[] getDeclaredConstraints(AnnotatedElement e) {
+        return 
Stream.of(e.getDeclaredAnnotations()).flatMap((Function<Annotation, 
Stream<Annotation>>) a -> {
+            final ConstraintAnnotationAttributes.Worker<? extends Annotation> 
analyzer =
+                
ConstraintAnnotationAttributes.VALUE.analyze(a.annotationType());
+            if (analyzer.isValid()) {
+                return Stream.of(analyzer.<Annotation[]> read(a));
+            }
+            return Stream.of(a);
+        }).filter(a -> 
a.annotationType().isAnnotationPresent(Constraint.class)).toArray(Annotation[]::new);
+    }
+
+    public static boolean declaresAttribute(Class<? extends Annotation> 
annotationType, String name) {
+        try {
+            annotationType.getDeclaredMethod(name);
+            return true;
+        } catch (NoSuchMethodException | SecurityException e) {
+            return false;
+        }
+    }
+
+    private final ApacheValidatorFactory validatorFactory;
+    private final LRUCache<Class<? extends Annotation>, Composition> 
compositions;
+
+    public AnnotationsManager(ApacheValidatorFactory validatorFactory) {
+        super();
+        this.validatorFactory = Validate.notNull(validatorFactory);
+        final String cacheSize =
+            
validatorFactory.getProperties().get(ConfigurationImpl.Properties.CONSTRAINTS_CACHE_SIZE);
+        try {
+            compositions = new LRUCache<>(Integer.parseInt(cacheSize));
+        } catch (NumberFormatException e) {
+            throw new IllegalStateException(String.format("Cannot parse value 
%s for configuration property %s",
+                cacheSize, 
ConfigurationImpl.Properties.CONSTRAINTS_CACHE_SIZE));
+        }
+    }
+
+    /**
+     * Retrieve the composing constraints for the specified constraint {@link 
Annotation}.
+     * 
+     * @param a
+     * @return {@link Annotation}[]
+     */
+    public Annotation[] getComposingConstraints(Annotation a) {
+        return compositions.computeIfAbsent(a.annotationType(), 
this::getComposition).getComponents(a);
+    }
+
+    public Set<ValidationTarget> supportedTargets(Class<? extends Annotation> 
constraintType) {
+        return 
validatorFactory.getConstraintsCache().getConstraintValidatorInfo(constraintType)
+            
.orElseGet(Collections::emptySet).stream().map(ConstraintValidatorInfo::getSupportedTargets)
+            .flatMap(Collection::stream).collect(Collectors.toSet());
+    }
+
+    private Composition getComposition(Class<? extends Annotation> 
annotationType) {
+        final Set<ValidationTarget> composedTargets = 
supportedTargets(annotationType);
+        final Composition result = new Composition(annotationType);
+        
Stream.of(result.components).map(Annotation::annotationType).forEach(at -> {
+            final Set<ValidationTarget> composingTargets = 
supportedTargets(at);
+            if (Collections.disjoint(composingTargets, composedTargets)) {
+                throw new ConstraintDefinitionException(
+                    String.format("Attempt to compose %s of %s but validator 
types are incompatible",
+                        annotationType.getName(), at.getName()));
+            }
+        });
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderCustomizableContextImpl.java
----------------------------------------------------------------------
diff --git 
a/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderCustomizableContextImpl.java
 
b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderCustomizableContextImpl.java
new file mode 100644
index 0000000..c0cff10
--- /dev/null
+++ 
b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderCustomizableContextImpl.java
@@ -0,0 +1,77 @@
+/*
+ * 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.util;
+
+import javax.validation.ConstraintValidatorContext;
+import 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderCustomizableContext;
+import 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeContextBuilder;
+import 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderCustomizableContext;
+import 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext;
+
+import org.apache.bval.jsr.job.ConstraintValidatorContextImpl;
+
+public class ContainerElementNodeBuilderCustomizableContextImpl
+    implements ContainerElementNodeBuilderCustomizableContext {
+    private final ConstraintValidatorContextImpl<?> context;
+    private final String template;
+    private final PathImpl path;
+    private NodeImpl node;
+
+    public 
ContainerElementNodeBuilderCustomizableContextImpl(ConstraintValidatorContextImpl<?>
 context, String template,
+        PathImpl path, String name, Class<?> containerType, Integer 
typeArgumentIndex) {
+        super();
+        this.context = context;
+        this.path = path;
+        this.template = template;
+        this.node = new NodeImpl.ContainerElementNodeImpl(name, containerType, 
typeArgumentIndex);
+    }
+
+    @Override
+    public ContainerElementNodeContextBuilder inIterable() {
+        node.setInIterable(true);
+        return new ContainerElementNodeContextBuilderImpl(context, template, 
path, node);
+    }
+
+    @Override
+    public NodeBuilderCustomizableContext addPropertyNode(String name) {
+        path.addNode(node);
+        return new NodeBuilderCustomizableContextImpl(context, template, path, 
name);
+    }
+
+    @Override
+    public LeafNodeBuilderCustomizableContext addBeanNode() {
+        path.addNode(node);
+        return new LeafNodeBuilderCustomizableContextImpl(context, template, 
path);
+    }
+
+    @Override
+    public ContainerElementNodeBuilderCustomizableContext 
addContainerElementNode(String name, Class<?> containerType,
+        Integer typeArgumentIndex) {
+        path.addNode(node);
+        node = new NodeImpl.ContainerElementNodeImpl(name, containerType, 
typeArgumentIndex);
+        return this;
+    }
+
+    @Override
+    public ConstraintValidatorContext addConstraintViolation() {
+        context.addError(template, path);
+        return context;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderDefinedContextImpl.java
----------------------------------------------------------------------
diff --git 
a/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderDefinedContextImpl.java
 
b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderDefinedContextImpl.java
new file mode 100644
index 0000000..6077d87
--- /dev/null
+++ 
b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeBuilderDefinedContextImpl.java
@@ -0,0 +1,65 @@
+/*
+ * 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.util;
+
+import javax.validation.ConstraintValidatorContext;
+import 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderCustomizableContext;
+import 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderDefinedContext;
+import 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderCustomizableContext;
+import 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext;
+
+import org.apache.bval.jsr.job.ConstraintValidatorContextImpl;
+
+public class ContainerElementNodeBuilderDefinedContextImpl implements 
ContainerElementNodeBuilderDefinedContext {
+    private final ConstraintValidatorContextImpl<?> context;
+    private final String template;
+    private final PathImpl path;
+
+    
ContainerElementNodeBuilderDefinedContextImpl(ConstraintValidatorContextImpl<?> 
context, String template,
+        PathImpl path) {
+        super();
+        this.context = context;
+        this.template = template;
+        this.path = path;
+    }
+
+    @Override
+    public NodeBuilderCustomizableContext addPropertyNode(String name) {
+        return new NodeBuilderCustomizableContextImpl(context, template, path, 
name);
+    }
+
+    @Override
+    public LeafNodeBuilderCustomizableContext addBeanNode() {
+        return new LeafNodeBuilderCustomizableContextImpl(context, template, 
path);
+    }
+
+    @Override
+    public ContainerElementNodeBuilderCustomizableContext 
addContainerElementNode(String name, Class<?> containerType,
+        Integer typeArgumentIndex) {
+        return new ContainerElementNodeBuilderCustomizableContextImpl(context, 
name, path, name, containerType,
+            typeArgumentIndex);
+    }
+
+    @Override
+    public ConstraintValidatorContext addConstraintViolation() {
+        context.addError(template, path);
+        return context;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeContextBuilderImpl.java
----------------------------------------------------------------------
diff --git 
a/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeContextBuilderImpl.java
 
b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeContextBuilderImpl.java
new file mode 100644
index 0000000..f05ef76
--- /dev/null
+++ 
b/bval-jsr/src/main/java/org/apache/bval/jsr/util/ContainerElementNodeContextBuilderImpl.java
@@ -0,0 +1,85 @@
+/*
+ * 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.util;
+
+import javax.validation.ConstraintValidatorContext;
+import 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderCustomizableContext;
+import 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderDefinedContext;
+import 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeContextBuilder;
+import 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderCustomizableContext;
+import 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext;
+
+import org.apache.bval.jsr.job.ConstraintValidatorContextImpl;
+
+public class ContainerElementNodeContextBuilderImpl implements 
ContainerElementNodeContextBuilder {
+    private final ConstraintValidatorContextImpl<?> context;
+    private final String template;
+    private final PathImpl path;
+    private final NodeImpl node;
+
+    ContainerElementNodeContextBuilderImpl(ConstraintValidatorContextImpl<?> 
context, String template,
+        PathImpl path, NodeImpl node) {
+        super();
+        this.context = context;
+        this.template = template;
+        this.path = path;
+        this.node = node;
+    }
+
+    @Override
+    public ContainerElementNodeBuilderDefinedContext atKey(Object key) {
+        node.setKey(key);
+        path.addNode(node);
+        return new ContainerElementNodeBuilderDefinedContextImpl(context, 
template, path);
+    }
+
+    @Override
+    public ContainerElementNodeBuilderDefinedContext atIndex(Integer index) {
+        node.setIndex(index);
+        path.addNode(node);
+        return new ContainerElementNodeBuilderDefinedContextImpl(context, 
template, path);
+    }
+
+    @Override
+    public NodeBuilderCustomizableContext addPropertyNode(String name) {
+        path.addNode(node);
+        return new NodeBuilderCustomizableContextImpl(context, name, path, 
name);
+    }
+
+    @Override
+    public LeafNodeBuilderCustomizableContext addBeanNode() {
+        path.addNode(node);
+        return new LeafNodeBuilderCustomizableContextImpl(context, template, 
path);
+    }
+
+    @Override
+    public ContainerElementNodeBuilderCustomizableContext 
addContainerElementNode(String name, Class<?> containerType,
+        Integer typeArgumentIndex) {
+        path.addNode(node);
+        return new ContainerElementNodeBuilderCustomizableContextImpl(context, 
template, path, name, containerType,
+            typeArgumentIndex);
+    }
+
+    @Override
+    public ConstraintValidatorContext addConstraintViolation() {
+        context.addError(template, path);
+        return context;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/Exceptions.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/Exceptions.java 
b/bval-jsr/src/main/java/org/apache/bval/jsr/util/Exceptions.java
new file mode 100644
index 0000000..f847424
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/Exceptions.java
@@ -0,0 +1,65 @@
+/*
+ *  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.util;
+
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+public class Exceptions {
+
+    public static <E extends Exception> E create(Function<String, E> fn, 
String format, Object... args) {
+        return fn.apply(String.format(format, args));
+    }
+
+    public static <E extends Exception, C extends Throwable> E 
create(BiFunction<String, ? super C, E> fn, C cause,
+        String format, Object... args) {
+        return fn.apply(String.format(format, args), cause);
+    }
+
+    public static <E extends Exception> void raise(Function<String, E> fn, 
String format, Object... args) throws E {
+        throw create(fn, format, args);
+    }
+
+    public static <E extends Exception> void raiseIf(boolean condition, 
Function<String, E> fn, String format,
+        Object... args) throws E {
+        if (condition) {
+            raise(fn, format, args);
+        }
+    }
+
+    public static <E extends Exception> void raiseUnless(boolean condition, 
Function<String, E> fn, String format,
+        Object... args) throws E {
+        raiseIf(!condition, fn, format, args);
+    }
+
+    public static <E extends Exception, C extends Throwable> void 
raise(BiFunction<String, ? super C, E> fn, C cause,
+        String format, Object... args) throws E {
+        throw create(fn, cause, format, args);
+    }
+
+    public static <E extends Exception, C extends Throwable> void 
raiseIf(boolean condition,
+        BiFunction<String, ? super C, E> fn, C cause, String format, Object... 
args) throws E {
+        if (condition) {
+            raise(fn, cause, format, args);
+        }
+    }
+
+    public static <E extends Exception, C extends Throwable> void 
raiseUnless(boolean condition,
+        BiFunction<String, ? super C, E> fn, C cause, String format, Object... 
args) throws E {
+        raiseIf(!condition, fn, cause, format, args);
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/LRUCache.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/LRUCache.java 
b/bval-jsr/src/main/java/org/apache/bval/jsr/util/LRUCache.java
new file mode 100644
index 0000000..3434017
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/LRUCache.java
@@ -0,0 +1,40 @@
+/*
+ * 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.util;
+
+import java.util.LinkedHashMap;
+
+public class LRUCache<K, V> extends LinkedHashMap<K, V> {
+    private static final long serialVersionUID = 1L;
+
+    private final int maximumCapacity;
+
+    public LRUCache(int maximumCapacity) {
+        super(16, 0.75f, true);
+        if (maximumCapacity < 1) {
+            throw new IllegalArgumentException("maximumCapacity must be > 0");
+        }
+        this.maximumCapacity = maximumCapacity;
+    }
+
+    @Override
+    protected boolean removeEldestEntry(java.util.Map.Entry<K, V> eldest) {
+        return super.removeEldestEntry(eldest) || size() >= maximumCapacity;
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/LeafNodeBuilderCustomizableContextImpl.java
----------------------------------------------------------------------
diff --git 
a/bval-jsr/src/main/java/org/apache/bval/jsr/util/LeafNodeBuilderCustomizableContextImpl.java
 
b/bval-jsr/src/main/java/org/apache/bval/jsr/util/LeafNodeBuilderCustomizableContextImpl.java
index efa9aeb..99305be 100644
--- 
a/bval-jsr/src/main/java/org/apache/bval/jsr/util/LeafNodeBuilderCustomizableContextImpl.java
+++ 
b/bval-jsr/src/main/java/org/apache/bval/jsr/util/LeafNodeBuilderCustomizableContextImpl.java
@@ -18,9 +18,10 @@
  */
 package org.apache.bval.jsr.util;
 
-import org.apache.bval.jsr.ConstraintValidatorContextImpl;
+import org.apache.bval.jsr.job.ConstraintValidatorContextImpl;
 
 import javax.validation.ConstraintValidatorContext;
+import 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderCustomizableContext;
 import 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderDefinedContext;
 import 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeContextBuilder;
 
@@ -43,8 +44,7 @@ public class LeafNodeBuilderCustomizableContextImpl
         }
 
         @Override
-        public LeafNodeBuilderDefinedContext atIndex(
-            Integer index) {
+        public LeafNodeBuilderDefinedContext atIndex(Integer index) {
             node.setIndex(index);
             return definedContext;
         }
@@ -55,16 +55,16 @@ public class LeafNodeBuilderCustomizableContextImpl
         }
     }
 
-    private final ConstraintValidatorContextImpl context;
+    private final ConstraintValidatorContextImpl<?> context;
     private final PathImpl path;
     private final String template;
     private final NodeImpl node;
 
-    public LeafNodeBuilderCustomizableContextImpl(final 
ConstraintValidatorContextImpl parent, String messageTemplate,
-        PathImpl propertyPath) {
-        context = parent;
-        template = messageTemplate;
-        path = propertyPath;
+    public LeafNodeBuilderCustomizableContextImpl(final 
ConstraintValidatorContextImpl<?> context, String template,
+        PathImpl path) {
+        this.context = context;
+        this.template = template;
+        this.path = path;
         node = new NodeImpl.BeanNodeImpl();
     }
 
@@ -81,4 +81,9 @@ public class LeafNodeBuilderCustomizableContextImpl
         return context;
     }
 
+    @Override
+    public LeafNodeBuilderCustomizableContext inContainer(Class<?> 
containerType, Integer typeArgumentIndex) {
+        node.inContainer(containerType, typeArgumentIndex);
+        return this;
+    }
 }

http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/Methods.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/Methods.java 
b/bval-jsr/src/main/java/org/apache/bval/jsr/util/Methods.java
new file mode 100644
index 0000000..9f98311
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/Methods.java
@@ -0,0 +1,45 @@
+/*
+ *  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.util;
+
+import java.beans.Introspector;
+import java.lang.reflect.Method;
+
+import org.apache.bval.util.Validate;
+
+public final class Methods {
+    public static boolean isGetter(Method m) {
+        if (m.getParameterCount() > 0) {
+            return false;
+        }
+        // TODO look for capital letter after verb?
+        if (Boolean.TYPE.equals(m.getReturnType()) && 
m.getName().startsWith("is")) {
+            return true;
+        }
+        return !Void.TYPE.equals(m.getReturnType()) && 
m.getName().startsWith("get");
+    }
+
+    public static String propertyName(Method getter) {
+        Validate.isTrue(isGetter(getter), "%s is not a getter", getter);
+        final String name = getter.getName();
+        final String suffix = name.startsWith("is") ? name.substring(2) : 
name.substring(3);
+        return Introspector.decapitalize(suffix);
+    }
+
+    private Methods() {
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderCustomizableContextImpl.java
----------------------------------------------------------------------
diff --git 
a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderCustomizableContextImpl.java
 
b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderCustomizableContextImpl.java
index ca058fc..6ec977c 100644
--- 
a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderCustomizableContextImpl.java
+++ 
b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderCustomizableContextImpl.java
@@ -18,38 +18,40 @@
  */
 package org.apache.bval.jsr.util;
 
-import org.apache.bval.jsr.ConstraintValidatorContextImpl;
-
 import javax.validation.ConstraintValidatorContext;
+import 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderCustomizableContext;
+import 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext;
 import javax.validation.ElementKind;
 
+import org.apache.bval.jsr.job.ConstraintValidatorContextImpl;
+
 /**
  * Description: implementation of {@link 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext}.<br/>
  */
 public final class NodeBuilderCustomizableContextImpl
     implements 
ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext
 {
-    private final ConstraintValidatorContextImpl parent;
-    private final String messageTemplate;
-    private final PathImpl propertyPath;
+    private final ConstraintValidatorContextImpl<?> context;
+    private final String template;
+    private final PathImpl path;
     private NodeImpl node;
 
     /**
      * Create a new NodeBuilderCustomizableContextImpl instance.
-     * @param contextImpl
+     * @param context
      * @param template
      * @param path
      * @param name
      */
-    public NodeBuilderCustomizableContextImpl(ConstraintValidatorContextImpl 
contextImpl, String template, PathImpl path,
+    public 
NodeBuilderCustomizableContextImpl(ConstraintValidatorContextImpl<?> context, 
String template, PathImpl path,
         String name) {
-        parent = contextImpl;
-        messageTemplate = template;
-        propertyPath = path;
+        this.context = context;
+        this.template = template;
+        this.path = path;
 
-        if (propertyPath.isRootPath() || propertyPath.getLeafNode().getKind() 
!= null) {
+        if (path.isRootPath() || path.getLeafNode().getKind() != null) {
             node = new NodeImpl.PropertyNodeImpl(name);
         } else {
-            node = propertyPath.removeLeafNode();
+            node = path.removeLeafNode();
             node.setName(name);
             node.setKind(ElementKind.PROPERTY); // enforce it
         }
@@ -61,7 +63,7 @@ public final class NodeBuilderCustomizableContextImpl
     @Override
     public 
ConstraintValidatorContext.ConstraintViolationBuilder.NodeContextBuilder 
inIterable() {
         node.setInIterable(true);
-        return new NodeContextBuilderImpl(parent, messageTemplate, 
propertyPath, node);
+        return new NodeContextBuilderImpl(context, template, path, node);
     }
 
     /**
@@ -75,15 +77,15 @@ public final class NodeBuilderCustomizableContextImpl
     @Override
     public 
ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext
 addPropertyNode(
         String name) {
-        propertyPath.addNode(node);
+        path.addNode(node);
         node = new NodeImpl.PropertyNodeImpl(name);
         return this;
     }
 
     @Override
     public 
ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderCustomizableContext
 addBeanNode() {
-        propertyPath.addNode(node);
-        return new LeafNodeBuilderCustomizableContextImpl(parent, 
messageTemplate, propertyPath);
+        path.addNode(node);
+        return new LeafNodeBuilderCustomizableContextImpl(context, template, 
path);
     }
 
     /**
@@ -91,10 +93,25 @@ public final class NodeBuilderCustomizableContextImpl
      */
     @Override
     public ConstraintValidatorContext addConstraintViolation() {
-        propertyPath.addNode(node);
+        path.addNode(node);
         node = null;
-        parent.addError(messageTemplate, propertyPath);
-        return parent;
+        context.addError(template, path);
+        return context;
+    }
+
+    @Override
+    public NodeBuilderCustomizableContext inContainer(Class<?> containerClass, 
Integer typeArgumentIndex) {
+        path.getLeafNode().inContainer(containerClass, typeArgumentIndex);
+        return this;
+    }
+
+    @Override
+    public ContainerElementNodeBuilderCustomizableContext 
addContainerElementNode(String name, Class<?> containerType,
+        Integer typeArgumentIndex) {
+        path.addNode(node);
+        node = new NodeImpl.ContainerElementNodeImpl(name, containerType, 
typeArgumentIndex);
+        return new ContainerElementNodeBuilderCustomizableContextImpl(context, 
template, path, name, containerType,
+            typeArgumentIndex);
     }
 
 }

http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderDefinedContextImpl.java
----------------------------------------------------------------------
diff --git 
a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderDefinedContextImpl.java
 
b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderDefinedContextImpl.java
index 5ce20b5..f695e84 100644
--- 
a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderDefinedContextImpl.java
+++ 
b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeBuilderDefinedContextImpl.java
@@ -18,18 +18,19 @@
  */
 package org.apache.bval.jsr.util;
 
-import org.apache.bval.jsr.ConstraintValidatorContextImpl;
-
 import javax.validation.ConstraintValidatorContext;
+import 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderCustomizableContext;
+
+import org.apache.bval.jsr.job.ConstraintValidatorContextImpl;
 
 /**
  * Description: Implementation of {@link NodeBuilderDefinedContext}.<br/>
  */
 public final class NodeBuilderDefinedContextImpl
     implements 
ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderDefinedContext 
{
-    private final ConstraintValidatorContextImpl parent;
-    private final String messageTemplate;
-    private final PathImpl propertyPath;
+    private final ConstraintValidatorContextImpl context;
+    private final String template;
+    private final PathImpl path;
 
     /**
      * Create a new NodeBuilderDefinedContextImpl instance.
@@ -38,9 +39,9 @@ public final class NodeBuilderDefinedContextImpl
      * @param path
      */
     public NodeBuilderDefinedContextImpl(ConstraintValidatorContextImpl 
contextImpl, String template, PathImpl path) {
-        parent = contextImpl;
-        messageTemplate = template;
-        propertyPath = path;
+        this.context = contextImpl;
+        this.template = template;
+        this.path = path;
     }
 
     /**
@@ -54,12 +55,12 @@ public final class NodeBuilderDefinedContextImpl
     @Override
     public 
ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext
 addPropertyNode(
         String name) {
-        return new NodeBuilderCustomizableContextImpl(parent, messageTemplate, 
propertyPath, name);
+        return new NodeBuilderCustomizableContextImpl(context, template, path, 
name);
     }
 
     @Override
     public 
ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderCustomizableContext
 addBeanNode() {
-        return new LeafNodeBuilderCustomizableContextImpl(parent, 
messageTemplate, propertyPath);
+        return new LeafNodeBuilderCustomizableContextImpl(context, template, 
path);
     }
 
     /**
@@ -67,7 +68,14 @@ public final class NodeBuilderDefinedContextImpl
      */
     @Override
     public ConstraintValidatorContext addConstraintViolation() {
-        parent.addError(messageTemplate, propertyPath);
-        return parent;
+        context.addError(template, path);
+        return context;
+    }
+
+    @Override
+    public ContainerElementNodeBuilderCustomizableContext 
addContainerElementNode(String name, Class<?> containerType,
+        Integer typeArgumentIndex) {
+        return new ContainerElementNodeBuilderCustomizableContextImpl(context, 
template, path, name, containerType,
+            typeArgumentIndex);
     }
 }

http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeContextBuilderImpl.java
----------------------------------------------------------------------
diff --git 
a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeContextBuilderImpl.java 
b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeContextBuilderImpl.java
index 80e5b8f..f32db9f 100644
--- 
a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeContextBuilderImpl.java
+++ 
b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeContextBuilderImpl.java
@@ -18,18 +18,19 @@
  */
 package org.apache.bval.jsr.util;
 
-import org.apache.bval.jsr.ConstraintValidatorContextImpl;
+import org.apache.bval.jsr.job.ConstraintValidatorContextImpl;
 
 import javax.validation.ConstraintValidatorContext;
+import 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.ContainerElementNodeBuilderCustomizableContext;
 import 
javax.validation.ConstraintValidatorContext.ConstraintViolationBuilder.NodeContextBuilder;
 
 /**
  * Description: Implementation of {@link NodeContextBuilder}.<br/>
  */
-final class NodeContextBuilderImpl implements 
ConstraintValidatorContext.ConstraintViolationBuilder.NodeContextBuilder {
-    private final ConstraintValidatorContextImpl parent;
-    private final String messageTemplate;
-    private final PathImpl propertyPath;
+public final class NodeContextBuilderImpl implements 
ConstraintValidatorContext.ConstraintViolationBuilder.NodeContextBuilder {
+    private final ConstraintValidatorContextImpl<?> context;
+    private final String template;
+    private final PathImpl path;
     // The name of the last "added" node, it will only be added if it has a 
non-null name
     // The actual incorporation in the path will take place when the 
definition of the current leaf node is complete
     private final NodeImpl node;
@@ -40,10 +41,10 @@ final class NodeContextBuilderImpl implements 
ConstraintValidatorContext.Constra
      * @param template
      * @param path
      */
-    NodeContextBuilderImpl(ConstraintValidatorContextImpl contextImpl, String 
template, PathImpl path, NodeImpl node) {
-        parent = contextImpl;
-        messageTemplate = template;
-        propertyPath = path;
+    NodeContextBuilderImpl(ConstraintValidatorContextImpl<?> contextImpl, 
String template, PathImpl path, NodeImpl node) {
+        this.context = contextImpl;
+        this.template = template;
+        this.path = path;
         this.node = node;
     }
 
@@ -53,8 +54,8 @@ final class NodeContextBuilderImpl implements 
ConstraintValidatorContext.Constra
     @Override
     public 
ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderDefinedContext 
atKey(Object key) {
         node.setKey(key);
-        propertyPath.addNode(node);
-        return new NodeBuilderDefinedContextImpl(parent, messageTemplate, 
propertyPath);
+        path.addNode(node);
+        return new NodeBuilderDefinedContextImpl(context, template, path);
     }
 
     /**
@@ -63,8 +64,8 @@ final class NodeContextBuilderImpl implements 
ConstraintValidatorContext.Constra
     @Override
     public 
ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderDefinedContext 
atIndex(Integer index) {
         node.setIndex(index);
-        propertyPath.addNode(node);
-        return new NodeBuilderDefinedContextImpl(parent, messageTemplate, 
propertyPath);
+        path.addNode(node);
+        return new NodeBuilderDefinedContextImpl(context, template, path);
     }
 
     /**
@@ -78,14 +79,14 @@ final class NodeContextBuilderImpl implements 
ConstraintValidatorContext.Constra
     @Override
     public 
ConstraintValidatorContext.ConstraintViolationBuilder.NodeBuilderCustomizableContext
 addPropertyNode(
         String name) {
-        propertyPath.addNode(node);
-        return new NodeBuilderCustomizableContextImpl(parent, messageTemplate, 
propertyPath, name);
+        path.addNode(node);
+        return new NodeBuilderCustomizableContextImpl(context, template, path, 
name);
     }
 
     @Override
     public 
ConstraintValidatorContext.ConstraintViolationBuilder.LeafNodeBuilderCustomizableContext
 addBeanNode() {
-        propertyPath.addNode(node);
-        return new LeafNodeBuilderCustomizableContextImpl(parent, 
messageTemplate, propertyPath);
+        path.addNode(node);
+        return new LeafNodeBuilderCustomizableContextImpl(context, template, 
path);
     }
 
     /**
@@ -93,9 +94,18 @@ final class NodeContextBuilderImpl implements 
ConstraintValidatorContext.Constra
      */
     @Override
     public ConstraintValidatorContext addConstraintViolation() {
-        propertyPath.addNode(node);
-        parent.addError(messageTemplate, propertyPath);
-        return parent;
+        path.addNode(node);
+        context.addError(template, path);
+        return context;
     }
 
-}
\ No newline at end of file
+    @Override
+    public ContainerElementNodeBuilderCustomizableContext 
addContainerElementNode(String name, Class<?> containerType,
+        Integer typeArgumentIndex) {
+        final NodeImpl node = new NodeImpl.ContainerElementNodeImpl(name, 
containerType, typeArgumentIndex);
+        path.addNode(node);
+        return new ContainerElementNodeBuilderCustomizableContextImpl(context, 
template, path, name, containerType,
+            typeArgumentIndex);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/a43c0b0c/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java 
b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java
index 5a08f0e..fcdc4cc 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/util/NodeImpl.java
@@ -21,16 +21,23 @@ package org.apache.bval.jsr.util;
 import javax.validation.ElementKind;
 import javax.validation.Path;
 import javax.validation.Path.Node;
+
 import java.io.Serializable;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Consumer;
 
 public class NodeImpl implements Path.Node, Serializable {
 
     private static final long serialVersionUID = 1L;
     private static final String INDEX_OPEN = "[";
     private static final String INDEX_CLOSE = "]";
-    private List<Class<?>> parameterTypes;
+
+    private static <T extends Path.Node> Optional<T> optional(Class<T> type, 
Object o) {
+        return Optional.ofNullable(o).filter(type::isInstance).map(type::cast);
+    }
 
     /**
      * Append a Node to the specified StringBuilder.
@@ -85,6 +92,9 @@ public class NodeImpl implements Path.Node, Serializable {
     private int parameterIndex;
     private Object key;
     private ElementKind kind;
+    private List<Class<?>> parameterTypes;
+    private Class<?> containerType;
+    private Integer typeArgumentIndex;
 
     /**
      * Create a new NodeImpl instance.
@@ -99,13 +109,18 @@ public class NodeImpl implements Path.Node, Serializable {
      * @param node
      */
     NodeImpl(Path.Node node) {
-        this.name = node.getName();
+        this(node.getName());
         this.inIterable = node.isInIterable();
         this.index = node.getIndex();
         this.key = node.getKey();
         this.kind = node.getKind();
     }
 
+    <T extends Path.Node> NodeImpl(Path.Node node, Class<T> nodeType, 
Consumer<T> handler) {
+        this(node);
+        
Optional.of(node).filter(nodeType::isInstance).map(nodeType::cast).ifPresent(handler);
+    }
+
     private NodeImpl() {
     }
 
@@ -213,29 +228,14 @@ public class NodeImpl implements Path.Node, Serializable {
         if (this == o) {
             return true;
         }
-        if (o == null || getClass() != o.getClass()) {
+        if (o == null || !getClass().equals(o.getClass())) {
             return false;
         }
 
         NodeImpl node = (NodeImpl) o;
 
-        if (inIterable != node.inIterable) {
-            return false;
-        }
-        if (index != null ? !index.equals(node.index) : node.index != null) {
-            return false;
-        }
-        if (key != null ? !key.equals(node.key) : node.key != null) {
-            return false;
-        }
-        if (name != null ? !name.equals(node.name) : node.name != null) {
-            return false;
-        }
-        if (kind != null ? !kind.equals(node.kind) : node.kind != null) {
-            return false;
-        }
-
-        return true;
+        return inIterable == node.inIterable && Objects.equals(index, 
node.index) && Objects.equals(key, node.key)
+            && Objects.equals(name, node.name) && kind == node.kind;
     }
 
     /**
@@ -243,12 +243,7 @@ public class NodeImpl implements Path.Node, Serializable {
      */
     @Override
     public int hashCode() {
-        int result = name != null ? name.hashCode() : 0;
-        result = 31 * result + (inIterable ? 1 : 0);
-        result = 31 * result + (index != null ? index.hashCode() : 0);
-        result = 31 * result + (key != null ? key.hashCode() : 0);
-        result = 31 * result + (kind != null ? kind.hashCode() : 0);
-        return result;
+        return Objects.hash(name, Boolean.valueOf(inIterable), index, key, 
kind);
     }
 
     public int getParameterIndex() {
@@ -263,12 +258,24 @@ public class NodeImpl implements Path.Node, Serializable {
         this.parameterTypes = parameterTypes;
     }
 
+    public Class<?> getContainerClass() {
+        return containerType;
+    }
+
+    public Integer getTypeArgumentIndex() {
+        return typeArgumentIndex;
+    }
+
+    public void inContainer(Class<?> containerType, Integer typeArgumentIndex) 
{
+        this.containerType = containerType;
+        this.typeArgumentIndex = typeArgumentIndex;
+    }
+
+    @SuppressWarnings("serial")
     public static class ParameterNodeImpl extends NodeImpl implements 
Path.ParameterNode {
         public ParameterNodeImpl(final Node cast) {
             super(cast);
-            if (ParameterNodeImpl.class.isInstance(cast)) {
-                
setParameterIndex(ParameterNodeImpl.class.cast(cast).getParameterIndex());
-            }
+            optional(Path.ParameterNode.class, cast).ifPresent(n -> 
setParameterIndex(n.getParameterIndex()));
         }
 
         public ParameterNodeImpl(final String name, final int idx) {
@@ -282,12 +289,11 @@ public class NodeImpl implements Path.Node, Serializable {
         }
     }
 
+    @SuppressWarnings("serial")
     public static class ConstructorNodeImpl extends NodeImpl implements 
Path.ConstructorNode {
         public ConstructorNodeImpl(final Node cast) {
             super(cast);
-            if (NodeImpl.class.isInstance(cast)) {
-                setParameterTypes(NodeImpl.class.cast(cast).parameterTypes);
-            }
+            optional(Path.ConstructorNode.class, cast).ifPresent(n -> 
setParameterTypes(n.getParameterTypes()));
         }
 
         public ConstructorNodeImpl(final String simpleName, List<Class<?>> 
paramTypes) {
@@ -301,6 +307,7 @@ public class NodeImpl implements Path.Node, Serializable {
         }
     }
 
+    @SuppressWarnings("serial")
     public static class CrossParameterNodeImpl extends NodeImpl implements 
Path.CrossParameterNode {
         public CrossParameterNodeImpl() {
             super("<cross-parameter>");
@@ -316,12 +323,11 @@ public class NodeImpl implements Path.Node, Serializable {
         }
     }
 
+    @SuppressWarnings("serial")
     public static class MethodNodeImpl extends NodeImpl implements 
Path.MethodNode {
         public MethodNodeImpl(final Node cast) {
             super(cast);
-            if (MethodNodeImpl.class.isInstance(cast)) {
-                
setParameterTypes(MethodNodeImpl.class.cast(cast).getParameterTypes());
-            }
+            optional(Path.MethodNode.class, cast).ifPresent(n -> 
setParameterTypes(n.getParameterTypes()));
         }
 
         public MethodNodeImpl(final String name, final List<Class<?>> classes) 
{
@@ -335,6 +341,7 @@ public class NodeImpl implements Path.Node, Serializable {
         }
     }
 
+    @SuppressWarnings("serial")
     public static class ReturnValueNodeImpl extends NodeImpl implements 
Path.ReturnValueNode {
         public ReturnValueNodeImpl(final Node cast) {
             super(cast);
@@ -350,6 +357,7 @@ public class NodeImpl implements Path.Node, Serializable {
         }
     }
 
+    @SuppressWarnings("serial")
     public static class PropertyNodeImpl extends NodeImpl implements 
Path.PropertyNode {
         public PropertyNodeImpl(final String name) {
             super(name);
@@ -357,6 +365,8 @@ public class NodeImpl implements Path.Node, Serializable {
 
         public PropertyNodeImpl(final Node cast) {
             super(cast);
+            optional(Path.PropertyNode.class, cast)
+                .ifPresent(n -> inContainer(n.getContainerClass(), 
n.getTypeArgumentIndex()));
         }
 
         @Override
@@ -365,6 +375,7 @@ public class NodeImpl implements Path.Node, Serializable {
         }
     }
 
+    @SuppressWarnings("serial")
     public static class BeanNodeImpl extends NodeImpl implements Path.BeanNode 
{
         public BeanNodeImpl() {
             // no-op
@@ -372,6 +383,8 @@ public class NodeImpl implements Path.Node, Serializable {
 
         public BeanNodeImpl(final Node cast) {
             super(cast);
+            optional(Path.BeanNode.class, cast)
+                .ifPresent(n -> inContainer(n.getContainerClass(), 
n.getTypeArgumentIndex()));
         }
 
         @Override
@@ -379,4 +392,24 @@ public class NodeImpl implements Path.Node, Serializable {
             return ElementKind.BEAN;
         }
     }
+
+    @SuppressWarnings("serial")
+    public static class ContainerElementNodeImpl extends NodeImpl implements 
Path.ContainerElementNode {
+
+        public ContainerElementNodeImpl(String name, Class<?> containerType, 
Integer typeArgumentIndex) {
+            super(name);
+            inContainer(containerType, typeArgumentIndex);
+        }
+
+        public ContainerElementNodeImpl(final Node cast) {
+            super(cast);
+            optional(Path.ContainerElementNode.class, cast)
+                .ifPresent(n -> inContainer(n.getContainerClass(), 
n.getTypeArgumentIndex()));
+        }
+
+        @Override
+        public ElementKind getKind() {
+            return ElementKind.CONTAINER_ELEMENT;
+        }
+    }
 }

Reply via email to