You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bval.apache.org by mb...@apache.org on 2018/02/21 20:25:03 UTC

[07/11] bval git commit: implement BV 2.0 against existing BVal unit tests

http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java
new file mode 100644
index 0000000..21816d7
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/DescriptorManager.java
@@ -0,0 +1,74 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.bval.jsr.descriptor;
+
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.validation.metadata.BeanDescriptor;
+import javax.validation.metadata.CascadableDescriptor;
+import javax.validation.metadata.ElementDescriptor;
+
+import org.apache.bval.jsr.ApacheValidatorFactory;
+import org.apache.bval.jsr.metadata.AnnotationBehaviorMergeStrategy;
+import org.apache.bval.jsr.metadata.CompositeBuilder;
+import org.apache.bval.jsr.metadata.HierarchyBuilder;
+import org.apache.bval.jsr.metadata.MetadataBuilder;
+import org.apache.bval.jsr.metadata.DualBuilder;
+import org.apache.bval.jsr.metadata.ReflectionBuilder;
+import org.apache.bval.util.Validate;
+
+public class DescriptorManager {
+    public static <D extends ElementDescriptor & CascadableDescriptor> boolean isConstrained(D descriptor) {
+        return descriptor.hasConstraints() || descriptor.isCascaded();
+    }
+
+    private final ApacheValidatorFactory validatorFactory;
+    private final ConcurrentMap<Class<?>, BeanD> beanDescriptors = new ConcurrentHashMap<>();
+    private final ReflectionBuilder reflectionBuilder;
+    private final MetadataReader metadataReader;
+
+    public DescriptorManager(ApacheValidatorFactory validatorFactory) {
+        super();
+        this.validatorFactory = Validate.notNull(validatorFactory, "validatorFactory");
+        this.reflectionBuilder = new ReflectionBuilder(validatorFactory);
+        this.metadataReader = new MetadataReader(validatorFactory);
+    }
+
+    public BeanDescriptor getBeanDescriptor(Class<?> beanClass) {
+        Validate.notNull(beanClass, IllegalArgumentException::new, "beanClass");
+        return beanDescriptors.computeIfAbsent(beanClass, k -> new BeanD(metadataReader.forBean(k, builder(k))));
+    }
+
+    private MetadataBuilder.ForBean builder(Class<?> beanClass) {
+        final MetadataBuilder.ForBean primaryBuilder =
+            new HierarchyBuilder(reflectionBuilder::forBean).forBean(beanClass);
+
+        final MetadataBuilder.ForBean customBuilder = new HierarchyBuilder(this::customBuilder).forBean(beanClass);
+
+        return customBuilder.isEmpty() ? primaryBuilder : DualBuilder.forBean(primaryBuilder, customBuilder);
+    }
+
+    private MetadataBuilder.ForBean customBuilder(Class<?> beanClass) {
+        final List<MetadataBuilder.ForBean> customBuilders =
+            validatorFactory.getMetadataBuilders().getCustomBuilders(beanClass);
+
+        return customBuilders.isEmpty() ? null : customBuilders.stream()
+            .collect(CompositeBuilder.with(AnnotationBehaviorMergeStrategy.consensus()).compose());
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java
new file mode 100644
index 0000000..c139773
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.bval.jsr.descriptor;
+
+import java.lang.annotation.ElementType;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.validation.metadata.ConstraintDescriptor;
+import javax.validation.metadata.ElementDescriptor;
+
+import org.apache.bval.util.Validate;
+import org.apache.bval.util.reflection.TypeUtils;
+
+public abstract class ElementD<E extends AnnotatedElement, R extends MetadataReader.ForElement<E, ?>>
+    implements ElementDescriptor {
+
+    public static abstract class NonRoot<P extends ElementD<?, ?>, E extends AnnotatedElement, R extends MetadataReader.ForElement<E, ?>>
+        extends ElementD<E, R> {
+
+        protected final P parent;
+
+        protected NonRoot(R reader, P parent) {
+            super(reader);
+            this.parent = Validate.notNull(parent, "parent");
+        }
+
+        public P getParent() {
+            return parent;
+        }
+
+        @Override
+        public final Type getGenericType() {
+            if (TypeUtils.containsTypeVariables(genericType)) {
+                final Map<TypeVariable<?>, Type> args =
+                    TypeUtils.getTypeArguments(parent.getGenericType(), Object.class);
+                return TypeUtils.unrollVariables(args, genericType);
+            }
+            return genericType;
+        }
+
+        @Override
+        final protected BeanD getBean() {
+            return parent.getBean();
+        }
+
+        @Override
+        public final List<Class<?>> getGroupSequence() {
+            return getBean().getGroupSequence();
+        }
+    }
+
+    protected final Type genericType;
+
+    private final E target;
+    private final ElementType elementType;
+    private final Set<ConstraintD<?>> constraints;
+
+    protected ElementD(R reader) {
+        super();
+        Validate.notNull(reader, "reader");
+        this.genericType = reader.meta.getType();
+        this.target = reader.meta.getHost();
+        this.elementType = reader.meta.getElementType();
+        this.constraints = reader.getConstraints();
+    }
+
+    @Override
+    public final boolean hasConstraints() {
+        return !constraints.isEmpty();
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Override
+    public final Set<ConstraintDescriptor<?>> getConstraintDescriptors() {
+        return (Set) constraints;
+    }
+
+    @Override
+    public final ConstraintFinder findConstraints() {
+        return new Finder(this);
+    }
+
+    public final ElementType getElementType() {
+        return elementType;
+    }
+
+    public final E getTarget() {
+        return target;
+    }
+
+    public abstract Type getGenericType();
+
+    public abstract List<Class<?>> getGroupSequence();
+
+    protected abstract BeanD getBean();
+
+    @Override
+    public String toString() {
+        return String.format("%s: %s", getClass().getSimpleName(), target);
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ExecutableD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ExecutableD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ExecutableD.java
new file mode 100644
index 0000000..db3df6c
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ExecutableD.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.bval.jsr.descriptor;
+
+import java.lang.reflect.Executable;
+import java.util.List;
+
+import javax.validation.metadata.CrossParameterDescriptor;
+import javax.validation.metadata.ExecutableDescriptor;
+import javax.validation.metadata.ParameterDescriptor;
+import javax.validation.metadata.ReturnValueDescriptor;
+
+public abstract class ExecutableD<E extends Executable, R extends MetadataReader.ForExecutable<E, R>, SELF extends ExecutableD<E, R, SELF>>
+    extends ElementD.NonRoot<BeanD, E, R> implements ExecutableDescriptor {
+
+    private final String name;
+    private final ReturnValueD<SELF, E> returnValue;
+    private final List<ParameterD<SELF>> parameters;
+    private final CrossParameterD<SELF, E> crossParameter;
+
+    @SuppressWarnings("unchecked")
+    protected ExecutableD(R reader, BeanD parent) {
+        super(reader, parent);
+
+        name = reader.meta.getName();
+
+        returnValue = reader.getReturnValueDescriptor((SELF) this);
+        parameters = reader.getParameterDescriptors((SELF) this);
+        crossParameter = reader.getCrossParameterDescriptor((SELF) this);
+    }
+
+    @Override
+    public final String getName() {
+        return name;
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Override
+    public final List<ParameterDescriptor> getParameterDescriptors() {
+        return (List) parameters;
+    }
+
+    @Override
+    public final CrossParameterDescriptor getCrossParameterDescriptor() {
+        return crossParameter;
+    }
+
+    @Override
+    public final ReturnValueDescriptor getReturnValueDescriptor() {
+        return returnValue;
+    }
+
+    @Override
+    public final boolean hasConstrainedParameters() {
+        return parameters.stream().anyMatch(this::isConstrained);
+    }
+
+    @Override
+    public final boolean hasConstrainedReturnValue() {
+        return isConstrained(returnValue);
+    }
+
+    protected abstract String nameOf(E e);
+
+    private boolean isConstrained(CascadableContainerD<?, ?> child) {
+        return child.isCascaded() || child.hasConstraints();
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/Finder.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/Finder.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/Finder.java
new file mode 100644
index 0000000..c01e069
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/Finder.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.bval.jsr.descriptor;
+
+import java.lang.annotation.ElementType;
+import java.util.EnumSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import javax.validation.groups.Default;
+import javax.validation.metadata.ConstraintDescriptor;
+import javax.validation.metadata.ElementDescriptor;
+import javax.validation.metadata.ElementDescriptor.ConstraintFinder;
+import javax.validation.metadata.Scope;
+
+import org.apache.bval.jsr.util.ToUnmodifiable;
+import org.apache.bval.util.Validate;
+
+class Finder implements ConstraintFinder, Supplier<Stream<ConstraintD<?>>> {
+    private Predicate<ConstraintD<?>> groups = c -> true;
+    private Predicate<ConstraintD<?>> scope;
+    private Predicate<ConstraintD<?>> elements;
+
+    private final ElementDescriptor owner;
+
+    Finder(ElementDescriptor owner) {
+        this.owner = Validate.notNull(owner, "owner");
+    }
+
+    @Override
+    public ConstraintFinder unorderedAndMatchingGroups(Class<?>... groups) {
+        this.groups = c -> Stream.of(groups).anyMatch(t -> {
+            final Set<Class<?>> constraintGroups = c.getGroups();
+            return constraintGroups.contains(t)
+                || constraintGroups.contains(Default.class) && c.getDeclaringClass().isAssignableFrom(t);
+        });
+        return this;
+    }
+
+    @Override
+    public ConstraintFinder lookingAt(Scope scope) {
+        this.scope = scope == Scope.HIERARCHY ? null : c -> c.getScope() == scope;
+        return this;
+    }
+
+    @Override
+    public ConstraintFinder declaredOn(ElementType... types) {
+        this.elements = c -> Stream.of(types).filter(Objects::nonNull)
+            .collect(Collectors.toCollection(() -> EnumSet.noneOf(ElementType.class))).contains(c.getDeclaredOn());
+
+        return this;
+    }
+
+    @Override
+    public Set<ConstraintDescriptor<?>> getConstraintDescriptors() {
+        return get().collect(ToUnmodifiable.set());
+    }
+
+    @Override
+    public Stream<ConstraintD<?>> get() {
+        return getConstraints().filter(filter());
+    }
+
+    @Override
+    public boolean hasConstraints() {
+        return getConstraints().anyMatch(filter());
+    }
+
+    private Stream<ConstraintD<?>> getConstraints() {
+        return owner.getConstraintDescriptors().stream().<ConstraintD<?>> map(c -> c.unwrap(ConstraintD.class));
+    }
+
+    private Predicate<ConstraintD<?>> filter() {
+        Predicate<ConstraintD<?>> result = groups;
+        if (scope != null) {
+            result = result.and(scope);
+        }
+        if (elements != null) {
+            result = result.and(elements);
+        }
+        return result;
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/GroupConversion.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/GroupConversion.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/GroupConversion.java
new file mode 100644
index 0000000..9ef724e
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/GroupConversion.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.descriptor;
+
+import java.util.Objects;
+import java.util.Optional;
+
+import javax.validation.metadata.GroupConversionDescriptor;
+
+import org.apache.bval.util.Lazy;
+import org.apache.bval.util.LazyInt;
+import org.apache.bval.util.Validate;
+
+public class GroupConversion implements GroupConversionDescriptor {
+    public static class Builder {
+        private final Class<?> from;
+
+        private Builder(Class<?> from) {
+            this.from = from;
+        }
+
+        public GroupConversion to(Class<?> to) {
+            return new GroupConversion(from, to);
+        }
+    }
+
+    public static Builder from(Class<?> from) {
+        return new Builder(from);
+    }
+
+    private final Class<?> from;
+    private final Class<?> to;
+    private final LazyInt hashCode;
+    private final Lazy<String> toString;
+
+    private GroupConversion(Class<?> from, Class<?> to) {
+        super();
+        this.from = Validate.notNull(from, "from");
+        this.to = Validate.notNull(to, "to");
+        this.hashCode = new LazyInt(() -> Objects.hash(this.from, this.to));
+        this.toString = new Lazy<>(
+            () -> String.format("%s from %s to %s", GroupConversion.class.getSimpleName(), this.from, this.to));
+    }
+
+    @Override
+    public Class<?> getFrom() {
+        return from;
+    }
+
+    @Override
+    public Class<?> getTo() {
+        return to;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        return obj == this
+            || Optional.ofNullable(obj).filter(GroupConversion.class::isInstance).map(GroupConversion.class::cast)
+                .filter(gc -> Objects.equals(from, gc.from) && Objects.equals(to, gc.to)).isPresent();
+    }
+
+    @Override
+    public int hashCode() {
+        return hashCode.getAsInt();
+    }
+
+    @Override
+    public String toString() {
+        return toString.get();
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java
new file mode 100644
index 0000000..c2f9f0c
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MetadataReader.java
@@ -0,0 +1,291 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.bval.jsr.descriptor;
+
+import java.lang.annotation.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.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import javax.validation.GroupDefinitionException;
+import javax.validation.GroupSequence;
+import javax.validation.ParameterNameProvider;
+import javax.validation.groups.Default;
+import javax.validation.metadata.PropertyDescriptor;
+import javax.validation.metadata.Scope;
+
+import org.apache.bval.jsr.ApacheValidatorFactory;
+import org.apache.bval.jsr.metadata.ContainerElementKey;
+import org.apache.bval.jsr.metadata.EmptyBuilder;
+import org.apache.bval.jsr.metadata.MetadataBuilder;
+import org.apache.bval.jsr.metadata.Metas;
+import org.apache.bval.jsr.metadata.Signature;
+import org.apache.bval.jsr.util.Methods;
+import org.apache.bval.jsr.util.ToUnmodifiable;
+import org.apache.bval.util.Exceptions;
+import org.apache.bval.util.Validate;
+import org.apache.bval.util.reflection.Reflection;
+
+class MetadataReader {
+
+    class ForElement<E extends AnnotatedElement, B extends MetadataBuilder.ForElement<E>> {
+        final Metas<E> meta;
+        protected final B builder;
+
+        ForElement(Metas<E> meta, B builder) {
+            super();
+            this.meta = Validate.notNull(meta, "meta");
+            this.builder = Validate.notNull(builder, "builder");
+        }
+
+        Set<ConstraintD<?>> getConstraints() {
+            return builder.getConstraintsByScope(meta).entrySet().stream()
+                .flatMap(e -> describe(e.getValue(), e.getKey(), meta)).collect(ToUnmodifiable.set());
+        }
+
+        private Stream<ConstraintD<?>> describe(Annotation[] constraints, Scope scope, Metas<?> meta) {
+            return Stream.of(constraints).map(c -> new ConstraintD<>(c, scope, meta, validatorFactory));
+        }
+    }
+
+    class ForBean extends MetadataReader.ForElement<Class<?>, MetadataBuilder.ForClass> {
+        private final MetadataBuilder.ForBean beanBuilder;
+
+        ForBean(Metas<Class<?>> meta, MetadataBuilder.ForBean builder) {
+            super(meta, Validate.notNull(builder, "builder").getClass(meta));
+            this.beanBuilder = builder;
+        }
+
+        Map<String, PropertyDescriptor> getProperties(BeanD parent) {
+            final Map<String, List<PropertyD<?>>> properties = new LinkedHashMap<>();
+            final Function<? super String, ? extends List<PropertyD<?>>> descriptorList = k -> new ArrayList<>();
+
+            beanBuilder.getFields(meta).forEach((f, builder) -> {
+                final Field fld = Reflection.find(meta.getHost(), t -> Reflection.getDeclaredField(t, f));
+                properties.computeIfAbsent(f, descriptorList).add(new PropertyD.ForField(
+                    new MetadataReader.ForContainer<>(new Metas.ForField(fld), builder), parent));
+            });
+
+            beanBuilder.getGetters(meta).forEach((g, builder) -> {
+                final Method getter = Reflection.find(meta.getHost(), t -> {
+                    return Stream.of(Reflection.getDeclaredMethods(t)).filter(Methods::isGetter)
+                        .filter(m -> g.equals(Methods.propertyName(m))).findFirst().orElse(null);
+                });
+                Exceptions.raiseIf(getter == null, IllegalStateException::new,
+                    "Getter method for property %s not found", g);
+
+                properties.computeIfAbsent(g, descriptorList).add(new PropertyD.ForMethod(
+                    new MetadataReader.ForContainer<>(new Metas.ForMethod(getter), builder), parent));
+            });
+            return properties.entrySet().stream().collect(ToUnmodifiable.map(Map.Entry::getKey, e -> {
+                final List<PropertyD<?>> delegates = e.getValue();
+
+                if (delegates.size() == 1) {
+                    return delegates.get(0);
+                }
+                final Set<PropertyD<?>> constrained =
+                    delegates.stream().filter(DescriptorManager::isConstrained).collect(Collectors.toSet());
+                if (constrained.isEmpty()) {
+                    return delegates.get(0);
+                }
+                if (constrained.size() == 1) {
+                    return constrained.iterator().next();
+                }
+                return new ComposedD.ForProperty(delegates);
+            }));
+        }
+
+        Map<Signature, MethodD> getMethods(BeanD parent) {
+            final Map<Signature, MetadataBuilder.ForExecutable<Method>> methodBuilders = beanBuilder.getMethods(meta);
+            if (methodBuilders.isEmpty()) {
+                return Collections.emptyMap();
+            }
+            final Map<Signature, MethodD> result = new LinkedHashMap<>();
+
+            methodBuilders.forEach((sig, builder) -> {
+                final Method m = Reflection.find(meta.getHost(),
+                    t -> Reflection.getDeclaredMethod(t, sig.getName(), sig.getParameterTypes()));
+
+                result.put(sig, new MethodD(new MetadataReader.ForMethod(new Metas.ForMethod(m), builder), parent));
+            });
+            return Collections.unmodifiableMap(result);
+        }
+
+        Map<Signature, ConstructorD> getConstructors(BeanD parent) {
+            final Map<Signature, MetadataBuilder.ForExecutable<Constructor<?>>> ctorBuilders =
+                beanBuilder.getConstructors(meta);
+
+            if (ctorBuilders.isEmpty()) {
+                return Collections.emptyMap();
+            }
+            final Map<Signature, ConstructorD> result = new LinkedHashMap<>();
+
+            ctorBuilders.forEach((sig, builder) -> {
+                final Constructor<?> c = Reflection.getDeclaredConstructor(meta.getHost(), sig.getParameterTypes());
+                result.put(sig,
+                    new ConstructorD(new MetadataReader.ForConstructor(new Metas.ForConstructor(c), builder), parent));
+            });
+            return Collections.unmodifiableMap(result);
+        }
+
+        List<Class<?>> getGroupSequence() {
+            List<Class<?>> result = builder.getGroupSequence(meta);
+            if (result == null) {
+                // resolve group sequence/Default redefinition up class hierarchy:
+                final Class<?> superclass = meta.getHost().getSuperclass();
+                if (superclass != null) {
+                    // attempt to mock parent sequence intent by appending this type immediately after supertype:
+                    result = ((ElementD<?, ?>) validatorFactory.getDescriptorManager().getBeanDescriptor(superclass))
+                        .getGroupSequence();
+                    if (result != null) {
+                        result = new ArrayList<>(result);
+                        result.add(result.indexOf(superclass) + 1, meta.getHost());
+                    }
+                }
+            }
+            if (result != null) {
+                Exceptions.raiseUnless(result.contains(meta.getHost()), GroupDefinitionException::new,
+                    "@%s for %s must contain %<s", GroupSequence.class.getSimpleName(), meta.getHost());
+                Exceptions.raiseIf(result.contains(Default.class), GroupDefinitionException::new,
+                    "@%s for %s must not contain %s", GroupSequence.class.getSimpleName(), meta.getHost(),
+                    Default.class.getName());
+            }
+            return result;
+        }
+    }
+
+    class ForContainer<E extends AnnotatedElement> extends ForElement<E, MetadataBuilder.ForContainer<E>> {
+
+        ForContainer(Metas<E> meta, MetadataBuilder.ForContainer<E> builder) {
+            super(meta, builder);
+        }
+
+        boolean isCascaded() {
+            return builder.isCascade(meta);
+        }
+
+        Set<GroupConversion> getGroupConversions() {
+            return builder.getGroupConversions(meta);
+        }
+
+        Set<ContainerElementTypeD> getContainerElementTypes(CascadableContainerD<?, ?> parent) {
+            final Map<ContainerElementKey, MetadataBuilder.ForContainer<AnnotatedType>> containerElementTypes =
+                builder.getContainerElementTypes(meta);
+
+            if (containerElementTypes.isEmpty()) {
+                return Collections.emptySet();
+            }
+            final Set<ContainerElementTypeD> result =
+                new TreeSet<>(Comparator.comparing(ContainerElementTypeD::getKey));
+
+            containerElementTypes.forEach((k, builder) -> {
+                result.add(new ContainerElementTypeD(k,
+                    new MetadataReader.ForContainer<>(new Metas.ForContainerElement(meta, k), builder), parent));
+            });
+            return Collections.unmodifiableSet(result);
+        }
+    }
+
+    abstract class ForExecutable<E extends Executable, SELF extends ForExecutable<E, SELF>>
+        extends ForElement<E, MetadataBuilder.ForElement<E>> {
+        private final MetadataBuilder.ForExecutable<E> executableBuilder;
+
+        ForExecutable(Metas<E> meta, MetadataBuilder.ForExecutable<E> executableBuilder) {
+            super(meta, EmptyBuilder.instance().forElement());
+            this.executableBuilder = Validate.notNull(executableBuilder, "executableBuilder");
+        }
+
+        <X extends ExecutableD<E, SELF, X>> List<ParameterD<X>> getParameterDescriptors(X parent) {
+            final Parameter[] parameters = meta.getHost().getParameters();
+
+            final List<String> parameterNames =
+                getParameterNames(validatorFactory.getParameterNameProvider(), meta.getHost());
+
+            final List<MetadataBuilder.ForContainer<Parameter>> builders = executableBuilder.getParameters(meta);
+
+            return IntStream.range(0, parameters.length).mapToObj(i -> {
+                final Metas.ForParameter param = new Metas.ForParameter(parameters[i], parameterNames.get(i));
+                return new ParameterD<>(param, i, new MetadataReader.ForContainer<>(param, builders.get(i)), parent);
+            }).collect(ToUnmodifiable.list());
+        }
+
+        <X extends ExecutableD<E, SELF, X>> CrossParameterD<X, E> getCrossParameterDescriptor(X parent) {
+            final Metas.ForCrossParameter<E> cp = new Metas.ForCrossParameter<>(meta);
+            return new CrossParameterD<>(new MetadataReader.ForElement<>(cp, executableBuilder.getCrossParameter(cp)),
+                parent);
+        }
+
+        <X extends ExecutableD<E, SELF, X>> ReturnValueD<X, E> getReturnValueDescriptor(X parent) {
+            return new ReturnValueD<>(new MetadataReader.ForContainer<>(meta, executableBuilder.getReturnValue(meta)),
+                parent);
+        }
+
+        abstract List<String> getParameterNames(ParameterNameProvider parameterNameProvider, E host);
+    }
+
+    class ForMethod extends ForExecutable<Method, ForMethod> {
+        ForMethod(Metas<Method> meta, MetadataBuilder.ForExecutable<Method> builder) {
+            super(meta, builder);
+        }
+
+        @Override
+        List<String> getParameterNames(ParameterNameProvider parameterNameProvider, Method host) {
+            return parameterNameProvider.getParameterNames(host);
+        }
+    }
+
+    class ForConstructor extends ForExecutable<Constructor<?>, ForConstructor> {
+
+        ForConstructor(Metas<Constructor<?>> meta, MetadataBuilder.ForExecutable<Constructor<?>> builder) {
+            super(meta, builder);
+        }
+
+        @Override
+        List<String> getParameterNames(ParameterNameProvider parameterNameProvider, Constructor<?> host) {
+            return parameterNameProvider.getParameterNames(host);
+        }
+    }
+
+    private final ApacheValidatorFactory validatorFactory;
+
+    MetadataReader(ApacheValidatorFactory validatorFactory) {
+        super();
+        this.validatorFactory = Validate.notNull(validatorFactory, "validatorFactory");
+    }
+
+    MetadataReader.ForBean forBean(Class<?> beanClass, MetadataBuilder.ForBean builder) {
+        return new MetadataReader.ForBean(new Metas.ForClass(beanClass), builder);
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MethodD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MethodD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MethodD.java
new file mode 100644
index 0000000..647569d
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/MethodD.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.bval.jsr.descriptor;
+
+import java.lang.reflect.Method;
+
+import javax.validation.metadata.MethodDescriptor;
+import javax.validation.metadata.MethodType;
+
+import org.apache.bval.jsr.util.Methods;
+
+class MethodD extends ExecutableD<Method, MetadataReader.ForMethod, MethodD> implements MethodDescriptor {
+    private final MethodType methodType;
+
+    MethodD(MetadataReader.ForMethod reader, BeanD parent) {
+        super(reader, parent);
+        methodType = Methods.isGetter(reader.meta.getHost()) ? MethodType.GETTER : MethodType.NON_GETTER;
+    }
+
+    @Override
+    public Class<?> getElementClass() {
+        return getTarget().getReturnType();
+    }
+
+    MethodType getMethodType() {
+        return methodType;
+    }
+
+    @Override
+    protected String nameOf(Method e) {
+        return e.getName();
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ParameterD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ParameterD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ParameterD.java
new file mode 100644
index 0000000..951eccb
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ParameterD.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.bval.jsr.descriptor;
+
+import java.lang.reflect.Parameter;
+
+import javax.validation.metadata.ParameterDescriptor;
+
+import org.apache.bval.jsr.metadata.Metas;
+import org.apache.bval.util.Validate;
+import org.apache.bval.util.reflection.TypeUtils;
+
+public class ParameterD<P extends ExecutableD<?, ?, P>> extends CascadableContainerD<P, Parameter>
+    implements ParameterDescriptor {
+
+    private final int index;
+    private final String name;
+    private final Class<?> type;
+
+    protected ParameterD(Metas.ForParameter meta, int index, MetadataReader.ForContainer<Parameter> reader, P parent) {
+        super(reader, parent);
+
+        Validate.isTrue(index >= 0 && index < meta.getHost().getDeclaringExecutable().getParameterCount(),
+            "Invalid parameter index %d", index);
+
+        this.index = index;
+
+        name = reader.meta.getName();
+        type = TypeUtils.getRawType(reader.meta.getType(), parent.getElementClass());
+    }
+
+    @Override
+    public Class<?> getElementClass() {
+        return type;
+    }
+
+    @Override
+    public int getIndex() {
+        return index;
+    }
+
+    @Override
+    public String getName() {
+        return name;
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/PropertyD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/PropertyD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/PropertyD.java
new file mode 100644
index 0000000..818e7e0
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/PropertyD.java
@@ -0,0 +1,106 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.bval.jsr.descriptor;
+
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+import javax.validation.metadata.PropertyDescriptor;
+
+import org.apache.bval.jsr.GraphContext;
+import org.apache.bval.jsr.util.Methods;
+import org.apache.bval.jsr.util.NodeImpl;
+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 PropertyD<E extends AnnotatedElement> extends CascadableContainerD<BeanD, E>
+    implements PropertyDescriptor {
+
+    static class ForField extends PropertyD<Field> {
+
+        ForField(MetadataReader.ForContainer<Field> reader, BeanD parent) {
+            super(reader, parent);
+        }
+
+        @Override
+        public String getPropertyName() {
+            return host.getName();
+        }
+
+        @Override
+        public Object getValue(Object parent) throws Exception {
+            final boolean mustUnset = Reflection.setAccessible(host, true);
+            try {
+                return host.get(parent);
+            } catch (IllegalAccessException e) {
+                throw new IllegalArgumentException(e);
+            } finally {
+                if (mustUnset) {
+                    Reflection.setAccessible(host, false);
+                }
+            }
+        }
+    }
+
+    static class ForMethod extends PropertyD<Method> {
+
+        ForMethod(MetadataReader.ForContainer<Method> reader, BeanD parent) {
+            super(reader, parent);
+        }
+
+        @Override
+        public String getPropertyName() {
+            return Methods.propertyName(host);
+        }
+
+        @Override
+        public Object getValue(Object parent) throws Exception {
+            final boolean mustUnset = Reflection.setAccessible(host, true);
+            try {
+                return host.invoke(parent);
+            } catch (IllegalAccessException | InvocationTargetException e) {
+                throw new IllegalArgumentException(e);
+            } finally {
+                if (mustUnset) {
+                    Reflection.setAccessible(host, false);
+                }
+            }
+        }
+    }
+
+    protected final E host;
+
+    protected PropertyD(MetadataReader.ForContainer<E> reader, BeanD parent) {
+        super(reader, parent);
+        this.host = reader.meta.getHost();
+    }
+
+    @Override
+    protected Stream<GraphContext> readImpl(GraphContext context) throws Exception {
+        final Supplier<NodeImpl> propertyNode = () -> new NodeImpl.PropertyNodeImpl(getPropertyName());
+        final Object value = getValue(context.getValue());
+        return Stream.of(context.child(propertyNode.get(), value));
+    }
+
+    public abstract Object getValue(Object parent) throws Exception;
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ReturnValueD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ReturnValueD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ReturnValueD.java
new file mode 100644
index 0000000..a2204fc
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ReturnValueD.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.    
+ */
+package org.apache.bval.jsr.descriptor;
+
+import java.lang.reflect.Executable;
+
+import javax.validation.metadata.ReturnValueDescriptor;
+
+public class ReturnValueD<P extends ExecutableD<?, ?, P>, E extends Executable> extends CascadableContainerD<P, E>
+    implements ReturnValueDescriptor {
+
+    ReturnValueD(MetadataReader.ForContainer<E> reader, P parent) {
+        super(reader, parent);
+    }
+
+    @Override
+    public Class<?> getElementClass() {
+        return parent.getElementClass();
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/Group.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/Group.java b/bval-jsr/src/main/java/org/apache/bval/jsr/groups/Group.java
index 4f9d10a..6a211ed 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/Group.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/groups/Group.java
@@ -18,6 +18,8 @@
  */
 package org.apache.bval.jsr.groups;
 
+import java.util.Objects;
+
 import javax.validation.groups.Default;
 
 /**
@@ -52,7 +54,7 @@ public final class Group {
      */
     @Override
     public String toString() {
-        return "Group{" + "group=" + group + '}';
+        return String.format("%s{group=%s}", Group.class.getSimpleName(), group);
     }
 
     /**
@@ -71,13 +73,10 @@ public final class Group {
         if (this == o) {
             return true;
         }
-        if (o == null || getClass() != o.getClass()) {
+        if (o == null || !getClass().equals(o.getClass())) {
             return false;
         }
-
-        Group group1 = (Group) o;
-
-        return group != null ? group.equals(group1.group) : group1.group == null;
+        return Objects.equals(group, ((Group) o).group);
     }
 
     /**
@@ -85,6 +84,6 @@ public final class Group {
      */
     @Override
     public int hashCode() {
-        return (group != null ? group.hashCode() : 0);
+        return Objects.hashCode(group);
     }
 }

http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupConversionDescriptorImpl.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupConversionDescriptorImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupConversionDescriptorImpl.java
index ba3a617..6d45ced 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupConversionDescriptorImpl.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupConversionDescriptorImpl.java
@@ -26,10 +26,9 @@ public class GroupConversionDescriptorImpl implements GroupConversionDescriptor
 
     public GroupConversionDescriptorImpl(final Group from, final Group to) {
         this.from = from.getGroup();
-        if (this.from.getAnnotation(GroupSequence.class) != null) {
+        if (this.from.isAnnotationPresent(GroupSequence.class)) {
             throw new ConstraintDeclarationException("from() can't get a group sequence");
         }
-
         this.to = to.getGroup();
     }
 

http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/Groups.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/Groups.java b/bval-jsr/src/main/java/org/apache/bval/jsr/groups/Groups.java
index 162bb66..3e7f008 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/Groups.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/groups/Groups.java
@@ -18,25 +18,29 @@
  */
 package org.apache.bval.jsr.groups;
 
-import javax.validation.GroupDefinitionException;
-import java.util.LinkedList;
+import java.util.ArrayList;
 import java.util.List;
 
+import javax.validation.GroupDefinitionException;
+
+import org.apache.bval.util.Exceptions;
+
 /**
- * Defines the order to validate groups during validation.
- * with some inspiration from reference implementation
+ * Defines the order to validate groups during validation. with some inspiration
+ * from reference implementation
  *
  * @author Roman Stumm
  */
 public class Groups {
-    /** The list of single groups. */
-    final List<Group> groups = new LinkedList<Group>();
-
     /** The list of sequences. */
-    final List<List<Group>> sequences = new LinkedList<List<Group>>();
+    private final List<List<Group>> sequences = new ArrayList<>();
+
+    /** The list of single groups. */
+    final List<Group> groups = new ArrayList<>();
 
     /**
      * Get the Groups.
+     * 
      * @return {@link List} of {@link Group}.
      */
     public List<Group> getGroups() {
@@ -45,6 +49,7 @@ public class Groups {
 
     /**
      * Get the Group sequences.
+     * 
      * @return {@link List} of {@link List} of {@link Group}
      */
     public List<List<Group>> getSequences() {
@@ -53,7 +58,9 @@ public class Groups {
 
     /**
      * Insert a {@link Group}.
-     * @param group to insert
+     * 
+     * @param group
+     *            to insert
      */
     void insertGroup(Group group) {
         if (!groups.contains(group)) {
@@ -63,52 +70,52 @@ public class Groups {
 
     /**
      * Insert a sequence.
-     * @param groups {@link List} of {@link Group} to insert
+     * 
+     * @param groups
+     *            {@link List} of {@link Group} to insert
      */
     void insertSequence(List<Group> groups) {
-        if (groups == null || groups.isEmpty()) {
-            return;
-        }
-
-        if (!sequences.contains(groups)) {
+        if (!(groups == null || groups.isEmpty() || sequences.contains(groups))) {
             sequences.add(groups);
         }
     }
 
     /**
-     * Assert that the default group can be expanded to <code>defaultGroups</code>.
+     * Assert that the default group can be expanded to
+     * <code>defaultGroups</code>.
+     * 
      * @param defaultGroups
      */
     public void assertDefaultGroupSequenceIsExpandable(List<Group> defaultGroups) {
         for (List<Group> groupList : sequences) {
-            int idx = groupList.indexOf(Group.DEFAULT);
-            if (idx != -1) {
+            final int idx = groupList.indexOf(Group.DEFAULT);
+            if (idx >= 0) {
                 ensureExpandable(groupList, defaultGroups, idx);
             }
         }
     }
 
     private void ensureExpandable(List<Group> groupList, List<Group> defaultGroupList, int defaultGroupIndex) {
-        for (int i = 0; i < defaultGroupList.size(); i++) {
-            Group group = defaultGroupList.get(i);
+        for (int i = 0, sz = defaultGroupList.size(); i < sz; i++) {
+            final Group group = defaultGroupList.get(i);
             if (group.isDefault()) {
                 continue; // the default group is the one we want to replace
             }
-            int index = groupList.indexOf(group); // sequence contains group of default group sequence
-            if (index == -1) {
-                continue; // if group is not in the sequence
+            // sequence contains group of default group sequence
+            final int index = groupList.indexOf(group);
+            if (index < 0) {
+                // group is not in the sequence
+                continue;
             }
-
             if ((i == 0 && index == defaultGroupIndex - 1)
                 || (i == defaultGroupList.size() - 1 && index == defaultGroupIndex + 1)) {
-                // if we are at the beginning or end of he defaultGroupSequence and the
-                // matches are either directly before or after we can continue,
-                // since we basically have two groups
+                // if we are at the beginning or end of he defaultGroupSequence
+                // and the matches are either directly before or after we can
+                // continue, since we basically have two groups
                 continue;
             }
-            throw new GroupDefinitionException(
-                "Unable to expand default group list" + defaultGroupList + " into sequence " + groupList);
+            Exceptions.raise(GroupDefinitionException::new, "Unable to expand default group list %s into sequence %s",
+                defaultGroupList, groupList);
         }
     }
-
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupsComputer.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupsComputer.java b/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupsComputer.java
index 398d6c3..ae6f629 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupsComputer.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupsComputer.java
@@ -18,126 +18,115 @@
  */
 package org.apache.bval.jsr.groups;
 
-import javax.validation.GroupDefinitionException;
-import javax.validation.GroupSequence;
-import javax.validation.ValidationException;
-import javax.validation.groups.Default;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashSet;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 
+import javax.validation.GroupDefinitionException;
+import javax.validation.GroupSequence;
+import javax.validation.ValidationException;
+import javax.validation.groups.Default;
+
+import org.apache.bval.util.Exceptions;
+import org.apache.bval.util.Validate;
+
 /**
  * Description: compute group order, based on the RI behavior as to guarantee
  * compatibility with interpretations of the spec.<br/>
  * Implementation is thread-safe.
  */
 public class GroupsComputer {
-    public static final Class<?>[] DEFAULT_GROUP = new Class<?>[] { Default.class };
+    public static final Class<?>[] DEFAULT_GROUP = { Default.class };
 
-    /** The default group array used in case any of the validate methods is called without a group. */
+    /**
+     * The default group array used in case any of the validate methods is
+     * called without a group.
+     */
     private static final Groups DEFAULT_GROUPS;
     static {
-        DEFAULT_GROUPS = new GroupsComputer().computeGroups(Arrays.asList(DEFAULT_GROUP));
+        DEFAULT_GROUPS = new Groups();
+        for (Class<?> g : DEFAULT_GROUP) {
+            DEFAULT_GROUPS.insertGroup(new Group(g));
+        }
     }
 
     /** caching resolved groups in a thread-safe map. */
-    private final Map<Class<?>, List<Group>> resolvedSequences = new ConcurrentHashMap<Class<?>, List<Group>>();
+    private final Map<Class<?>, List<Group>> resolvedSequences = new ConcurrentHashMap<>();
 
     /**
      * Compute groups from an array of group classes.
+     * 
      * @param groups
      * @return {@link Groups}
      */
-    public Groups computeGroups(Class<?>[] groups) {
-        if (groups == null) {
-            throw new IllegalArgumentException("null passed as group");
-        }
-
-        // if no groups is specified use the default
-        if (groups.length == 0) {
-            return DEFAULT_GROUPS;
-        }
-
+    @SafeVarargs
+    public final Groups computeGroups(Class<?>... groups) {
+        Exceptions.raiseIf(groups == null, IllegalArgumentException::new, "null validation groups specified");
         return computeGroups(Arrays.asList(groups));
     }
 
     /**
      * Main compute implementation.
+     * 
      * @param groups
      * @return {@link Groups}
      */
     protected Groups computeGroups(Collection<Class<?>> groups) {
-        if (groups == null || groups.size() == 0) {
-            throw new IllegalArgumentException("At least one group has to be specified.");
+        Validate.notNull(groups, "groups");
+
+        if (groups.isEmpty() || Arrays.asList(DEFAULT_GROUP).equals(new ArrayList<>(groups))) {
+            return DEFAULT_GROUPS;
         }
+        Exceptions.raiseIf(groups.stream().anyMatch(Objects::isNull), IllegalArgumentException::new,
+            "Null group specified");
 
         for (final Class<?> clazz : groups) {
-            if (clazz == null) {
-                throw new IllegalArgumentException("At least one group has to be specified.");
-            }
-
-            if (!clazz.isInterface()) {
-                throw new ValidationException("A group has to be an interface. " + clazz.getName() + " is not.");
-            }
+            Exceptions.raiseUnless(clazz.isInterface(), ValidationException::new,
+                "A group must be an interface. %s is not.", clazz);
         }
-
-        Groups chain = new Groups();
+        final Groups chain = new Groups();
         for (Class<?> clazz : groups) {
-            GroupSequence anno = clazz.getAnnotation(GroupSequence.class);
+            final GroupSequence anno = clazz.getAnnotation(GroupSequence.class);
             if (anno == null) {
-                Group group = new Group(clazz);
-                chain.insertGroup(group);
+                chain.insertGroup(new Group(clazz));
                 insertInheritedGroups(clazz, chain);
-            } else {
-                insertSequence(clazz, anno, chain);
+                continue;
             }
+            chain.insertSequence(
+                resolvedSequences.computeIfAbsent(clazz, g -> resolveSequence(g, anno, new HashSet<>())));
         }
-
         return chain;
     }
 
     private void insertInheritedGroups(Class<?> clazz, Groups chain) {
         for (Class<?> extendedInterface : clazz.getInterfaces()) {
-            Group group = new Group(extendedInterface);
-            chain.insertGroup(group);
+            chain.insertGroup(new Group(extendedInterface));
             insertInheritedGroups(extendedInterface, chain);
         }
     }
 
-    private void insertSequence(Class<?> clazz, GroupSequence anno, Groups chain) {
-        List<Group> sequence;
-        if (resolvedSequences.containsKey(clazz)) {
-            sequence = resolvedSequences.get(clazz);
-        } else {
-            sequence = resolveSequence(clazz, anno, new HashSet<Class<?>>());
-        }
-        chain.insertSequence(sequence);
-    }
-
     private List<Group> resolveSequence(Class<?> group, GroupSequence sequenceAnnotation,
         Set<Class<?>> processedSequences) {
-        if (processedSequences.contains(group)) {
-            throw new GroupDefinitionException("Cyclic dependency in groups definition");
-        } else {
-            processedSequences.add(group);
-        }
-        List<Group> resolvedGroupSequence = new LinkedList<Group>();
-        Class<?>[] sequenceArray = sequenceAnnotation.value();
-        for (Class<?> clazz : sequenceArray) {
-            GroupSequence anno = clazz.getAnnotation(GroupSequence.class);
+        Exceptions.raiseUnless(processedSequences.add(group), GroupDefinitionException::new,
+            "Cyclic dependency in groups definition");
+
+        final List<Group> resolvedGroupSequence = new ArrayList<>();
+        for (Class<?> clazz : sequenceAnnotation.value()) {
+            final GroupSequence anno = clazz.getAnnotation(GroupSequence.class);
             if (anno == null) {
-                resolvedGroupSequence.add(new Group(clazz)); // group part of sequence
+                // group part of sequence
+                resolvedGroupSequence.add(new Group(clazz));
             } else {
-                List<Group> tmpSequence = resolveSequence(clazz, anno, processedSequences); // recursion!
-                resolvedGroupSequence.addAll(tmpSequence);
+                // recursion!
+                resolvedGroupSequence.addAll(resolveSequence(clazz, anno, processedSequences));
             }
         }
-        resolvedSequences.put(group, resolvedGroupSequence);
         return resolvedGroupSequence;
     }
 }

http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ConstraintValidatorContextImpl.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ConstraintValidatorContextImpl.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ConstraintValidatorContextImpl.java
new file mode 100644
index 0000000..c3cc3a7
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ConstraintValidatorContextImpl.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.bval.jsr.job;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.validation.ClockProvider;
+import javax.validation.ConstraintValidatorContext;
+import javax.validation.ConstraintViolation;
+import javax.validation.MessageInterpolator;
+import javax.validation.Path;
+import javax.validation.ValidationException;
+import javax.validation.metadata.ConstraintDescriptor;
+import javax.validation.metadata.CrossParameterDescriptor;
+
+import org.apache.bval.jsr.descriptor.ComposedD;
+import org.apache.bval.jsr.descriptor.ConstraintD;
+import org.apache.bval.jsr.descriptor.CrossParameterD;
+import org.apache.bval.jsr.util.ContainerElementNodeBuilderCustomizableContextImpl;
+import org.apache.bval.jsr.util.LeafNodeBuilderCustomizableContextImpl;
+import org.apache.bval.jsr.util.NodeBuilderCustomizableContextImpl;
+import org.apache.bval.jsr.util.NodeBuilderDefinedContextImpl;
+import org.apache.bval.jsr.util.NodeImpl;
+import org.apache.bval.jsr.util.PathImpl;
+import org.apache.bval.util.Exceptions;
+import org.apache.bval.util.Lazy;
+import org.apache.bval.util.Validate;
+
+public class ConstraintValidatorContextImpl<T> implements ConstraintValidatorContext, MessageInterpolator.Context {
+    private class ConstraintViolationBuilderImpl implements ConstraintValidatorContext.ConstraintViolationBuilder {
+        private final String template;
+        private final PathImpl path;
+
+        ConstraintViolationBuilderImpl(String template, PathImpl path) {
+            this.template = template;
+            this.path = path;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public NodeBuilderDefinedContext addNode(String name) {
+            PathImpl p;
+            if (path.isRootPath()) {
+                p = PathImpl.create();
+                p.getLeafNode().setName(name);
+            } else {
+                p = PathImpl.copy(path);
+                p.addNode(new NodeImpl(name));
+            }
+            return new NodeBuilderDefinedContextImpl(ConstraintValidatorContextImpl.this, template, p);
+        }
+
+        @Override
+        public NodeBuilderCustomizableContext addPropertyNode(String name) {
+            return new NodeBuilderCustomizableContextImpl(ConstraintValidatorContextImpl.this, template, path, name);
+        }
+
+        @Override
+        public LeafNodeBuilderCustomizableContext addBeanNode() {
+            return new LeafNodeBuilderCustomizableContextImpl(ConstraintValidatorContextImpl.this, template, path);
+        }
+
+        @Override
+        public NodeBuilderDefinedContext addParameterNode(int index) {
+            Exceptions.raiseUnless(frame.descriptor instanceof CrossParameterDescriptor, ValidationException::new,
+                "Cannot add parameter node for %s", frame.descriptor.getClass().getName());
+
+            final CrossParameterD<?, ?> crossParameter =
+                ComposedD.unwrap(frame.descriptor, CrossParameterD.class).findFirst().get();
+
+            final String parameterName = crossParameter.getParent().getParameterDescriptors().get(index).getName();
+
+            final NodeImpl node = new NodeImpl.ParameterNodeImpl(parameterName, index);
+            if (!path.isRootPath()) {
+                path.removeLeafNode();
+            }
+            path.addNode(node);
+            return new NodeBuilderDefinedContextImpl(ConstraintValidatorContextImpl.this, template, path);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public ConstraintValidatorContext addConstraintViolation() {
+            addError(template, path);
+            return ConstraintValidatorContextImpl.this;
+        }
+
+        @Override
+        public ContainerElementNodeBuilderCustomizableContext addContainerElementNode(String name,
+            Class<?> containerType, Integer typeArgumentIndex) {
+            return new ContainerElementNodeBuilderCustomizableContextImpl(ConstraintValidatorContextImpl.this, template,
+                path, name, containerType, typeArgumentIndex);
+        }
+    }
+
+    private final ValidationJob<T>.Frame<?> frame;
+    private final ConstraintD<?> constraint;
+    private final Lazy<Set<ConstraintViolation<T>>> violations = new Lazy<>(HashSet::new);
+    private boolean defaultConstraintViolationDisabled;
+
+    /**
+     * Temporary for code migration
+     */
+    // TODO delete
+    @Deprecated
+    protected ConstraintValidatorContextImpl() {
+        this.frame = null;
+        this.constraint = null;
+    }
+
+    ConstraintValidatorContextImpl(ValidationJob<T>.Frame<?> frame, ConstraintD<?> constraint) {
+        super();
+        this.frame = Validate.notNull(frame, "frame");
+        this.constraint = Validate.notNull(constraint, "constraint");
+    }
+
+    @Override
+    public void disableDefaultConstraintViolation() {
+        this.defaultConstraintViolationDisabled = true;
+    }
+
+    @Override
+    public String getDefaultConstraintMessageTemplate() {
+        return constraint.getMessageTemplate();
+    }
+
+    @Override
+    public ConstraintViolationBuilder buildConstraintViolationWithTemplate(String messageTemplate) {
+        return new ConstraintViolationBuilderImpl(messageTemplate, frame.context.getPath());
+    }
+
+    @Override
+    public ClockProvider getClockProvider() {
+        return frame.getJob().validatorContext.getClockProvider();
+    }
+
+    @Override
+    public <U> U unwrap(Class<U> type) {
+        try {
+            return type.cast(this);
+        } catch (ClassCastException e) {
+            throw new ValidationException(e);
+        }
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public void addError(String messageTemplate, Path propertyPath) {
+        violations.get().add(((ValidationJob) frame.getJob()).createViolation(messageTemplate, this, propertyPath));
+    }
+
+    ValidationJob<T>.Frame<?> getFrame() {
+        return frame;
+    }
+
+    Set<ConstraintViolation<T>> getRequiredViolations() {
+        if (!violations.optional().isPresent()) {
+            Exceptions.raiseIf(defaultConstraintViolationDisabled, ValidationException::new,
+                "Expected custom constraint violation(s)");
+
+            addError(getDefaultConstraintMessageTemplate(), frame.context.getPath());
+        }
+        return violations.get();
+    }
+
+    @Override
+    public ConstraintDescriptor<?> getConstraintDescriptor() {
+        return constraint;
+    }
+
+    @Override
+    public Object getValidatedValue() {
+        return frame.context.getValue();
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateBean.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateBean.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateBean.java
new file mode 100644
index 0000000..dc3fab5
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateBean.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.bval.jsr.job;
+
+import javax.validation.Path;
+
+import org.apache.bval.jsr.ApacheFactoryContext;
+import org.apache.bval.jsr.ConstraintViolationImpl;
+import org.apache.bval.jsr.GraphContext;
+import org.apache.bval.jsr.descriptor.BeanD;
+import org.apache.bval.jsr.descriptor.ConstraintD;
+import org.apache.bval.jsr.util.PathImpl;
+import org.apache.bval.util.Validate;
+
+public final class ValidateBean<T> extends ValidationJob<T> {
+
+    private final T bean;
+
+    ValidateBean(ApacheFactoryContext validatorContext, T bean, Class<?>[] groups) {
+        super(validatorContext, groups);
+        this.bean = Validate.notNull(bean, IllegalArgumentException::new, "bean");
+    }
+
+    @Override
+    protected Frame<BeanD> computeBaseFrame() {
+        return new BeanFrame(new GraphContext(validatorContext, PathImpl.create(), bean));
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    protected Class<T> getRootBeanClass() {
+        return (Class<T>) bean.getClass();
+    }
+
+    @Override
+    ConstraintViolationImpl<T> createViolation(String messageTemplate, ConstraintValidatorContextImpl<T> context,
+        Path propertyPath) {
+        final String message = validatorContext.getMessageInterpolator().interpolate(messageTemplate, context);
+
+        return new ConstraintViolationImpl<>(messageTemplate, message, bean, context.getFrame().getBean(), propertyPath,
+            context.getFrame().context.getValue(), context.getConstraintDescriptor(), getRootBeanClass(),
+            context.getConstraintDescriptor().unwrap(ConstraintD.class).getDeclaredOn(), null, null);
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/3f287a7a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateParameters.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateParameters.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateParameters.java
new file mode 100644
index 0000000..c0d866b
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateParameters.java
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.bval.jsr.job;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.stream.IntStream;
+
+import javax.validation.ConstraintViolation;
+import javax.validation.ParameterNameProvider;
+import javax.validation.Path;
+import javax.validation.metadata.ExecutableDescriptor;
+
+import org.apache.bval.jsr.ApacheFactoryContext;
+import org.apache.bval.jsr.ConstraintViolationImpl;
+import org.apache.bval.jsr.GraphContext;
+import org.apache.bval.jsr.descriptor.ConstraintD;
+import org.apache.bval.jsr.descriptor.CrossParameterD;
+import org.apache.bval.jsr.descriptor.ParameterD;
+import org.apache.bval.jsr.util.NodeImpl;
+import org.apache.bval.jsr.util.PathImpl;
+import org.apache.bval.util.Exceptions;
+import org.apache.bval.util.Lazy;
+import org.apache.bval.util.Validate;
+import org.apache.bval.util.reflection.TypeUtils;
+
+public abstract class ValidateParameters<E extends Executable, T> extends ValidationJob<T> {
+
+    public static class ForMethod<T> extends ValidateParameters<Method, T> {
+
+        private final T object;
+
+        ForMethod(ApacheFactoryContext validatorContext, T object, Method executable, Object[] parameterValues,
+            Class<?>[] groups) {
+            super(validatorContext, object, executable, parameterValues, groups);
+            this.object = Validate.notNull(object, IllegalArgumentException::new, "object");
+        }
+
+        @Override
+        protected ExecutableDescriptor describe() {
+            return validatorContext.getDescriptorManager().getBeanDescriptor(object.getClass())
+                .getConstraintsForMethod(executable.getName(), executable.getParameterTypes());
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        protected Class<T> getRootBeanClass() {
+            return (Class<T>) object.getClass();
+        }
+
+        @Override
+        protected List<String> getParameterNames(ParameterNameProvider parameterNameProvider) {
+            return parameterNameProvider.getParameterNames(executable);
+        }
+
+        @Override
+        protected T getRootBean() {
+            return object;
+        }
+    }
+
+    public static class ForConstructor<T> extends ValidateParameters<Constructor<? extends T>, T> {
+
+        ForConstructor(ApacheFactoryContext validatorContext, Constructor<? extends T> executable,
+            Object[] parameterValues, Class<?>[] groups) {
+            super(validatorContext, null, executable, parameterValues, groups);
+        }
+
+        @Override
+        protected ExecutableDescriptor describe() {
+            return validatorContext.getDescriptorManager().getBeanDescriptor(executable.getDeclaringClass())
+                .getConstraintsForConstructor(executable.getParameterTypes());
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        protected Class<T> getRootBeanClass() {
+            return (Class<T>) executable.getDeclaringClass();
+        }
+
+        @Override
+        protected List<String> getParameterNames(ParameterNameProvider parameterNameProvider) {
+            return parameterNameProvider.getParameterNames(executable);
+        }
+
+        @Override
+        protected T getRootBean() {
+            return null;
+        }
+    }
+
+    class ParametersFrame extends Frame<CrossParameterD<?, ?>> {
+        private final ExecutableDescriptor executableDescriptor;
+
+        protected ParametersFrame(ExecutableDescriptor executableDescriptor, GraphContext context) {
+            super(null, (CrossParameterD<?, ?>) Validate.notNull(executableDescriptor, "executableDescriptor")
+                .getCrossParameterDescriptor(), context);
+            this.executableDescriptor = executableDescriptor;
+        }
+
+        @Override
+        void recurse(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
+            executableDescriptor.getParameterDescriptors().stream()
+                .map(pd -> new SproutFrame<ParameterD<?>>(this, (ParameterD<?>) pd, parameter(pd.getIndex())))
+                .forEach(f -> f.process(group, sink));
+        }
+
+        @Override
+        Object getBean() {
+            return object;
+        }
+    }
+
+    private static final String PARAMETERS_DO_NOT_MATCH = "Parameters do not match";
+
+    protected final T object;
+    protected final E executable;
+    protected final Lazy<List<String>> parameterNames =
+        new Lazy<>(() -> getParameterNames(validatorContext.getParameterNameProvider()));
+
+    private final Object[] parameterValues;
+
+    ValidateParameters(ApacheFactoryContext validatorContext, T object, E executable, Object[] parameterValues,
+        Class<?>[] groups) {
+        super(validatorContext, groups);
+        this.object = object;
+        this.executable = Validate.notNull(executable, IllegalArgumentException::new, "executable");
+        this.parameterValues =
+            Validate.notNull(parameterValues, IllegalArgumentException::new, "parameterValues").clone();
+
+        final Type[] genericParameterTypes = executable.getGenericParameterTypes();
+        Exceptions.raiseUnless(parameterValues.length == genericParameterTypes.length, IllegalArgumentException::new,
+            PARAMETERS_DO_NOT_MATCH);
+        IntStream.range(0, genericParameterTypes.length)
+            .forEach(n -> Exceptions.raiseUnless(TypeUtils.isInstance(parameterValues[n], genericParameterTypes[n]),
+                IllegalArgumentException::new, PARAMETERS_DO_NOT_MATCH));
+    }
+
+    @Override
+    protected Frame<?> computeBaseFrame() {
+        final PathImpl cp = PathImpl.create();
+        cp.addNode(new NodeImpl.CrossParameterNodeImpl());
+        return new ParametersFrame(describe(), new GraphContext(validatorContext, cp, parameterValues));
+    }
+
+    protected abstract ExecutableDescriptor describe();
+
+    protected abstract List<String> getParameterNames(ParameterNameProvider parameterNameProvider);
+
+    protected abstract T getRootBean();
+
+    @Override
+    ConstraintViolationImpl<T> createViolation(String messageTemplate, ConstraintValidatorContextImpl<T> context,
+        Path propertyPath) {
+
+        final String message = validatorContext.getMessageInterpolator().interpolate(messageTemplate, context);
+
+        return new ConstraintViolationImpl<T>(messageTemplate, message, getRootBean(), context.getFrame().getBean(),
+            propertyPath, context.getFrame().context.getValue(), context.getConstraintDescriptor(), getRootBeanClass(),
+            context.getConstraintDescriptor().unwrap(ConstraintD.class).getDeclaredOn(), null, parameterValues);
+    }
+
+    private GraphContext parameter(int i) {
+        final PathImpl path = PathImpl.create();
+        path.addNode(new NodeImpl.ParameterNodeImpl(parameterNames.get().get(i), i));
+        return new GraphContext(validatorContext, path, parameterValues[i]);
+    }
+}