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/04/12 18:01:28 UTC

[1/2] bval git commit: rework use of groups, introducing GroupStrategy interface + implementations. Passes 1 additional TCK test AND allows validation of non-sequential groups without making multiple passes over the graph

Repository: bval
Updated Branches:
  refs/heads/bv2 d1ddb57b8 -> f5bdeaf10


rework use of groups, introducing GroupStrategy interface + implementations. Passes 1 additional TCK test AND allows validation of non-sequential groups without making multiple passes over the graph


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

Branch: refs/heads/bv2
Commit: 657def169b32d209c200c1aecb2a534be892d208
Parents: 5fd197b
Author: Matt Benson <mb...@apache.org>
Authored: Thu Apr 12 13:00:58 2018 -0500
Committer: Matt Benson <mb...@apache.org>
Committed: Thu Apr 12 13:00:58 2018 -0500

----------------------------------------------------------------------
 .../java/org/apache/bval/jsr/GraphContext.java  |  18 ++
 .../org/apache/bval/jsr/descriptor/BeanD.java   |  10 +-
 .../apache/bval/jsr/descriptor/ConstraintD.java |  12 +-
 .../bval/jsr/descriptor/DescriptorManager.java  |   6 +
 .../apache/bval/jsr/descriptor/ElementD.java    |   8 +-
 .../org/apache/bval/jsr/descriptor/Finder.java  |   7 +-
 .../bval/jsr/descriptor/MetadataReader.java     | 111 ++++++---
 .../java/org/apache/bval/jsr/groups/Group.java  | 120 ++++++++-
 .../apache/bval/jsr/groups/GroupStrategy.java   | 241 +++++++++++++++++++
 .../java/org/apache/bval/jsr/groups/Groups.java |  56 +++--
 .../apache/bval/jsr/groups/GroupsComputer.java  |  11 +-
 .../apache/bval/jsr/job/ValidateParameters.java |  17 +-
 .../apache/bval/jsr/job/ValidateProperty.java   |   5 +-
 .../org/apache/bval/jsr/job/ValidationJob.java  | 201 ++++++++--------
 .../bval/jsr/groups/GroupSequenceTest.java      |  42 ++--
 .../bval/jsr/groups/GroupsComputerTest.java     |  11 +-
 16 files changed, 649 insertions(+), 227 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/bval/blob/657def16/bval-jsr/src/main/java/org/apache/bval/jsr/GraphContext.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/GraphContext.java b/bval-jsr/src/main/java/org/apache/bval/jsr/GraphContext.java
index 6b93940..9ab4141 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/GraphContext.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/GraphContext.java
@@ -21,6 +21,7 @@ package org.apache.bval.jsr;
 import java.lang.reflect.Type;
 import java.lang.reflect.TypeVariable;
 import java.util.Map;
+import java.util.Objects;
 
 import javax.validation.Path;
 import javax.validation.ValidationException;
@@ -100,6 +101,23 @@ public class GraphContext {
     public String toString() {
         return String.format("%s: %s at '%s'", getClass().getSimpleName(), value, path);
     }
+    
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+        if (obj == null || !obj.getClass().equals(getClass())) {
+            return false;
+        }
+        final GraphContext other = (GraphContext) obj;
+        return other.validatorContext == validatorContext && other.value == value && other.getPath().equals(path);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(validatorContext, value, path);
+    }
 
     public ContainerElementKey runtimeKey(ContainerElementKey key) {
         Validate.notNull(key);

http://git-wip-us.apache.org/repos/asf/bval/blob/657def16/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/BeanD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/BeanD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/BeanD.java
index 5feaddf..f6b6473 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/BeanD.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/BeanD.java
@@ -20,7 +20,6 @@ package org.apache.bval.jsr.descriptor;
 
 import java.lang.reflect.Type;
 import java.util.EnumSet;
-import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
@@ -31,6 +30,7 @@ import javax.validation.metadata.MethodDescriptor;
 import javax.validation.metadata.MethodType;
 import javax.validation.metadata.PropertyDescriptor;
 
+import org.apache.bval.jsr.groups.GroupStrategy;
 import org.apache.bval.jsr.metadata.Signature;
 import org.apache.bval.jsr.util.ToUnmodifiable;
 import org.apache.bval.util.Exceptions;
@@ -39,17 +39,17 @@ import org.apache.bval.util.StringUtils;
 public class BeanD<T> extends ElementD<Class<T>, MetadataReader.ForBean<T>> implements BeanDescriptor {
 
     private final Class<T> beanClass;
-    private final List<Class<?>> groupSequence;
     private final Map<String, PropertyDescriptor> propertiesMap;
     private final Set<PropertyDescriptor> properties;
     private final Map<Signature, ConstructorD<T>> constructors;
     private final Map<Signature, MethodD> methods;
+    private final GroupStrategy groupStrategy;
 
     BeanD(MetadataReader.ForBean<T> reader) {
         super(reader);
         this.beanClass = reader.meta.getHost();
 
-        groupSequence = reader.getGroupSequence();
+        groupStrategy = reader.getGroupStrategy();
         propertiesMap = reader.getProperties(this);
         properties = propertiesMap.values().stream().filter(DescriptorManager::isConstrained).collect(ToUnmodifiable.set());
         constructors = reader.getConstructors(this);
@@ -112,8 +112,8 @@ public class BeanD<T> extends ElementD<Class<T>, MetadataReader.ForBean<T>> impl
     }
 
     @Override
-    public List<Class<?>> getGroupSequence() {
-        return groupSequence;
+    public GroupStrategy getGroupStrategy() {
+        return groupStrategy;
     }
 
     public final Type getGenericType() {

http://git-wip-us.apache.org/repos/asf/bval/blob/657def16/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ConstraintD.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ConstraintD.java b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ConstraintD.java
index a5e528b..423520d 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ConstraintD.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ConstraintD.java
@@ -23,7 +23,6 @@ import java.lang.annotation.ElementType;
 import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Executable;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
@@ -38,7 +37,6 @@ import javax.validation.ConstraintValidator;
 import javax.validation.Payload;
 import javax.validation.ReportAsSingleViolation;
 import javax.validation.ValidationException;
-import javax.validation.groups.Default;
 import javax.validation.metadata.ConstraintDescriptor;
 import javax.validation.metadata.Scope;
 import javax.validation.metadata.ValidateUnwrappedValue;
@@ -91,7 +89,7 @@ public class ConstraintD<A extends Annotation> implements ConstraintDescriptor<A
         this.meta = Validate.notNull(meta, "meta");
 
         payload = computePayload();
-        groups = computeGroups();
+        groups = set(() -> read(ConstraintAnnotationAttributes.GROUPS, Optionality.REQUIRED));
         reportAsSingle = annotation.annotationType().isAnnotationPresent(ReportAsSingleViolation.class);
         valueUnwrapping = computeValidateUnwrappedValue();
         attributes = AnnotationsManager.readAttributes(annotation);
@@ -218,14 +216,6 @@ public class ConstraintD<A extends Annotation> implements ConstraintDescriptor<A
         return skip ? ValidateUnwrappedValue.SKIP : ValidateUnwrappedValue.DEFAULT;
     }
 
-    private Set<Class<?>> computeGroups() {
-        final Class<?>[] groups = read(ConstraintAnnotationAttributes.GROUPS, Optionality.REQUIRED);
-        if (groups.length == 0) {
-            return Collections.singleton(Default.class);
-        }
-        return set(() -> groups);
-    }
-
     private Set<Class<? extends Payload>> computePayload() {
         final Set<Class<? extends Payload>> result =
             set(() -> read(ConstraintAnnotationAttributes.PAYLOAD, Optionality.REQUIRED));

http://git-wip-us.apache.org/repos/asf/bval/blob/657def16/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
index dff4f77..9495f7a 100644
--- 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
@@ -43,6 +43,12 @@ public class DescriptorManager {
             || !descriptor.getConstrainedContainerElementTypes().isEmpty());
     }
 
+    public static <D extends ElementDescriptor & CascadableDescriptor & ContainerDescriptor> boolean isCascaded(
+        D descriptor) {
+        return descriptor != null && (descriptor.isCascaded()
+            || descriptor.getConstrainedContainerElementTypes().stream().anyMatch(DescriptorManager::isCascaded));
+    }
+
     public static <E extends ExecutableDescriptor> boolean isConstrained(E descriptor) {
         return descriptor != null && (descriptor.hasConstrainedParameters() || descriptor.hasConstrainedReturnValue());
     }

http://git-wip-us.apache.org/repos/asf/bval/blob/657def16/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
index e7c81c6..9fb5c98 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/descriptor/ElementD.java
@@ -22,13 +22,13 @@ 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.jsr.groups.GroupStrategy;
 import org.apache.bval.jsr.groups.GroupsComputer;
 import org.apache.bval.jsr.metadata.Meta;
 import org.apache.bval.util.Validate;
@@ -67,8 +67,8 @@ public abstract class ElementD<E extends AnnotatedElement, R extends MetadataRea
         }
 
         @Override
-        public final List<Class<?>> getGroupSequence() {
-            return getBean().getGroupSequence();
+        public final GroupStrategy getGroupStrategy() {
+            return getBean().getGroupStrategy();
         }
     }
 
@@ -117,7 +117,7 @@ public abstract class ElementD<E extends AnnotatedElement, R extends MetadataRea
 
     public abstract Type getGenericType();
 
-    public abstract List<Class<?>> getGroupSequence();
+    public abstract GroupStrategy getGroupStrategy();
 
     @Override
     public String toString() {

http://git-wip-us.apache.org/repos/asf/bval/blob/657def16/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
index ad5d541..600b44e 100644
--- 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
@@ -48,7 +48,8 @@ import org.apache.bval.util.Validate;
 
 class Finder implements ConstraintFinder {
     private static Stream<Group> allGroups(Groups groups) {
-        return Stream.concat(groups.getGroups().stream(), groups.getSequences().stream().flatMap(Collection::stream));
+        return Stream.concat(groups.getGroups().stream(),
+            groups.getSequences().stream().map(Group.Sequence::getGroups).flatMap(Collection::stream));
     }
 
     private volatile Predicate<ConstraintD<?>> groups = c -> true;
@@ -118,7 +119,9 @@ class Finder implements ConstraintFinder {
 
     private Groups computeDefaultSequence() {
         final ElementD<?, ?> element = firstAtomicElementDescriptor();
-        Collection<Class<?>> redef = element.getGroupSequence();
+        Collection<Class<?>> redef = 
+        element.getGroupStrategy().getGroups().stream().map(Group::getGroup).collect(Collectors.toList());
+        
         if (redef == null) {
             return GroupsComputer.DEFAULT_GROUPS;
         }

http://git-wip-us.apache.org/repos/asf/bval/blob/657def16/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
index 1673107..112aa81 100644
--- 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
@@ -26,15 +26,20 @@ import java.lang.reflect.Executable;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
 import java.lang.reflect.Parameter;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.Deque;
+import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.TreeSet;
 import java.util.function.Function;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
@@ -51,13 +56,16 @@ import javax.validation.metadata.Scope;
 
 import org.apache.bval.jsr.ApacheValidatorFactory;
 import org.apache.bval.jsr.ConstraintAnnotationAttributes;
+import org.apache.bval.jsr.groups.Group;
 import org.apache.bval.jsr.groups.GroupConversion;
+import org.apache.bval.jsr.groups.GroupStrategy;
 import org.apache.bval.jsr.groups.GroupsComputer;
 import org.apache.bval.jsr.metadata.ContainerElementKey;
 import org.apache.bval.jsr.metadata.EmptyBuilder;
 import org.apache.bval.jsr.metadata.Meta;
 import org.apache.bval.jsr.metadata.MetadataBuilder;
 import org.apache.bval.jsr.metadata.Signature;
+import org.apache.bval.jsr.util.AnnotationsManager;
 import org.apache.bval.jsr.util.Methods;
 import org.apache.bval.jsr.util.ToUnmodifiable;
 import org.apache.bval.jsr.xml.AnnotationProxyBuilder;
@@ -65,6 +73,7 @@ import org.apache.bval.util.Exceptions;
 import org.apache.bval.util.ObjectUtils;
 import org.apache.bval.util.Validate;
 import org.apache.bval.util.reflection.Reflection;
+import org.apache.bval.util.reflection.Reflection.Interfaces;
 
 class MetadataReader {
 
@@ -79,16 +88,16 @@ class MetadataReader {
         }
 
         Set<ConstraintD<?>> getConstraints() {
-            return builder.getConstraintDeclarationMap(meta).entrySet().stream().flatMap(e -> {
-                final Meta<E> m = e.getKey();
-                final Class<?> declaredBy = m.getDeclaringClass();
-                final Scope scope = declaredBy.equals(beanClass) ? Scope.LOCAL_ELEMENT : Scope.HIERARCHY;
-                return Stream.of(e.getValue())
-                    .peek(
+            return builder.getConstraintDeclarationMap(meta).entrySet().stream().filter(e -> e.getValue().length > 0)
+                .flatMap(e -> {
+                    final Meta<E> m = e.getKey();
+                    final Class<?> declaredBy = m.getDeclaringClass();
+                    final Scope scope = declaredBy.equals(beanClass) ? Scope.LOCAL_ELEMENT : Scope.HIERARCHY;
+                    return Stream.of(e.getValue()).peek(
                         c -> validatorFactory.getAnnotationsManager().validateConstraintDefinition(c.annotationType()))
-                    .map(c -> rewriteConstraint(c, declaredBy))
-                    .map(c -> new ConstraintD<>(c, scope, m, validatorFactory));
-            }).collect(ToUnmodifiable.set());
+                        .map(c -> rewriteConstraint(c, declaredBy))
+                        .map(c -> new ConstraintD<>(c, scope, m, validatorFactory));
+                }).collect(ToUnmodifiable.set());
         }
 
         ApacheValidatorFactory getValidatorFactory() {
@@ -133,8 +142,8 @@ class MetadataReader {
 
             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 Meta.ForField(fld), builder), parent));
+                properties.computeIfAbsent(f, descriptorList).add(
+                    new PropertyD.ForField(new MetadataReader.ForContainer<>(new Meta.ForField(fld), builder), parent));
             });
             beanBuilder.getGetters(meta).forEach((g, builder) -> {
                 final Method getter = Methods.getter(meta.getHost(), g);
@@ -205,34 +214,68 @@ class MetadataReader {
             return Collections.unmodifiableMap(result);
         }
 
-        List<Class<?>> getGroupSequence() {
-            List<Class<?>> result = builder.getGroupSequence(meta);
+        GroupStrategy getGroupStrategy() {
             final Class<T> host = meta.getHost();
-            if (result == null) {
-                // resolve group sequence/Default redefinition up class hierarchy:
-                final Class<?> superclass = host.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, host);
+            if (host.isInterface()) {
+                return validatorFactory.getGroupsComputer().computeGroups(host).asStrategy();
+            }
+            final GroupStrategy parentStrategy = Optional.ofNullable(host.getSuperclass()).filter(JDK.negate())
+                .map(validatorFactory.getDescriptorManager()::getBeanDescriptor).map(BeanD.class::cast)
+                .map(BeanD::getGroupStrategy).orElse(null);
+
+            final List<Class<?>> groupSequence = builder.getGroupSequence(meta);
+
+            final Set<Group> parentGroups = parentStrategy == null ? null : parentStrategy.getGroups();
+
+            Group localGroup = Group.of(host);
+            if (groupSequence == null) {
+                final Set<Group> groups = new HashSet<>();
+                groups.add(localGroup);
+
+                for (Class<?> t : Reflection.hierarchy(host, Interfaces.INCLUDE)) {
+                    if (JDK.test(t)) {
+                        continue;
                     }
+                    if (!t.isInterface()) {
+                        continue;
+                    }
+                    if (AnnotationsManager.isAnnotationDirectlyPresent(t, GroupSequence.class)) {
+                        continue;
+                    }
+                    final Group g = Group.of(t);
+                    if (parentGroups != null && parentGroups.contains(g)) {
+                        continue;
+                    }
+                    groups.add(g);
                 }
+                final GroupStrategy strategy = GroupStrategy.simple(groups);
+                return parentStrategy == null ? strategy : GroupStrategy.composite(strategy, parentStrategy);
             }
-            if (result == null) {
-                return null;
+            if (groupSequence.contains(Default.class)) {
+                Exceptions.raise(GroupDefinitionException::new, "@%s for %s must not contain %s",
+                    GroupSequence.class.getSimpleName(), host, Default.class.getName());
             }
-            if (!result.contains(host)) {
+            if (!groupSequence.contains(host)) {
                 Exceptions.raise(GroupDefinitionException::new, "@%s for %s must contain %<s",
                     GroupSequence.class.getSimpleName(), host);
             }
-            if (result.contains(Default.class)) {
-                Exceptions.raise(GroupDefinitionException::new, "@%s for %s must not contain %s",
-                    GroupSequence.class.getSimpleName(), host, Default.class.getName());
+            final Group.Sequence result =
+                Group.sequence(groupSequence.stream().map(Group::of).collect(Collectors.toList()));
+
+            final Deque<Group> expanded = new ArrayDeque<>();
+            for (Class<?> t : Reflection.hierarchy(host, Interfaces.INCLUDE)) {
+                if (JDK.test(t)) {
+                    continue;
+                }
+                if (t.isInterface() && AnnotationsManager.isAnnotationDirectlyPresent(t, GroupSequence.class)) {
+                    continue;
+                }
+                expanded.push(Group.of(t));
             }
-            return Collections.unmodifiableList(result);
+            if (expanded.size() == 1) {
+                return result;
+            }
+            return result.redefining(Collections.singletonMap(localGroup, GroupStrategy.simple(expanded)));
         }
     }
 
@@ -261,7 +304,8 @@ class MetadataReader {
                 groupConversions.stream().map(GroupConversion::getFrom)
                     .forEach(from -> Exceptions.raiseIf(from.isAnnotationPresent(GroupSequence.class),
                         ConstraintDeclarationException::new,
-                        "Invalid group conversion declared on %s from group sequence %s", f -> f.args(meta.describeHost(), from)));
+                        "Invalid group conversion declared on %s from group sequence %s",
+                        f -> f.args(meta.describeHost(), from)));
             }
             return groupConversions;
         }
@@ -336,7 +380,8 @@ class MetadataReader {
 
     class ForConstructor<T> extends ForExecutable<Constructor<? extends T>, ForConstructor<T>> {
 
-        ForConstructor(Meta<Constructor<? extends T>> meta, MetadataBuilder.ForExecutable<Constructor<? extends T>> builder) {
+        ForConstructor(Meta<Constructor<? extends T>> meta,
+            MetadataBuilder.ForExecutable<Constructor<? extends T>> builder) {
             super(meta, builder);
         }
 
@@ -346,6 +391,8 @@ class MetadataReader {
         }
     }
 
+    private static final Predicate<Class<?>> JDK = t -> t.getName().startsWith("java.");
+
     private final ApacheValidatorFactory validatorFactory;
     private final Class<?> beanClass;
 

http://git-wip-us.apache.org/repos/asf/bval/blob/657def16/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 6a211ed..d7dd994 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,19 +18,109 @@
  */
 package org.apache.bval.jsr.groups;
 
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Map;
 import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.function.Predicate;
 
+import javax.validation.GroupDefinitionException;
 import javax.validation.groups.Default;
 
+import org.apache.bval.util.Exceptions;
+import org.apache.bval.util.ObjectWrapper;
+import org.apache.bval.util.Validate;
+
 /**
  * Immutable object that wraps an interface representing a single group.
  */
-public final class Group {
+public final class Group implements GroupStrategy {
+    /**
+     * Models a group sequence.
+     */
+    public static final class Sequence extends GroupStrategy.Composite {
+        private static Set<Group> validGroups(Collection<Group> groups, Function<Group, ? extends GroupStrategy> mapper) {
+            final Set<Group> result = new LinkedHashSet<>();
+            final ObjectWrapper<Group> prev = new ObjectWrapper<>();
+
+            groups.stream().map(g -> Optional.of(g).<GroupStrategy> map(mapper).orElse(g)).map(GroupStrategy::getGroups)
+                .flatMap(Collection::stream).forEach(g -> {
+                    // only permit duplicates if they are contiguous:
+                    if (result.add(g)) {
+                        prev.accept(g);
+                        return;
+                    }
+                    if (!g.equals(prev.get())) {
+                        Exceptions.raise(GroupDefinitionException::new, "Invalid group sequence %s specified", groups);
+                    }
+                });
+            return result;
+        }
+
+        private final Set<Group> groups;
+
+        private Sequence(Collection<Group> groups) {
+            super(groups, true);
+            this.groups = Collections.unmodifiableSet(validGroups(groups, Function.identity()));
+        }
+
+        @Override
+        public Set<Group> getGroups() {
+            return groups;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("Group sequence: %s", groups);
+        }
+
+        @Override
+        public GroupStrategy redefining(Map<Group, ? extends GroupStrategy> redefinitions) {
+            if (Collections.disjoint(redefinitions.keySet(), groups)) {
+                return this;
+            }
+            final Set<GroupStrategy> components = new LinkedHashSet<>();
+
+            final Set<Group> mappedGroups;
+            try {
+                mappedGroups = validGroups(groups, g -> {
+                    final GroupStrategy result = Optional.of(g).<GroupStrategy> map(redefinitions::get).orElse(g);
+                    components.add(result);
+                    return result;
+                });
+            } catch (GroupDefinitionException e) {
+                throw Exceptions.create(GroupDefinitionException::new, "Could not expand %s using %s", this,
+                    redefinitions);
+            }
+            if (components.equals(mappedGroups)) {
+                return new Sequence(mappedGroups);
+            }
+            return new GroupStrategy.Composite(components, ordered);
+        }
+    }
+
     /**
      * the Default Group
      */
     public static final Group DEFAULT = new Group(Default.class);
 
+    public static final Group of(Class<?> group) {
+        return new Group(group);
+    }
+
+    public static final Sequence sequence(Group... groups) {
+        return sequence(Arrays.asList(groups));
+    }
+
+    public static final Sequence sequence(Collection<Group> groups) {
+        return new Sequence(groups);
+    }
+
     private final Class<?> group;
 
     /**
@@ -38,7 +128,7 @@ public final class Group {
      * @param group
      */
     public Group(Class<?> group) {
-        this.group = group;
+        this.group = Validate.notNull(group);
     }
 
     /**
@@ -70,13 +160,7 @@ public final class Group {
      */
     @Override
     public boolean equals(Object o) {
-        if (this == o) {
-            return true;
-        }
-        if (o == null || !getClass().equals(o.getClass())) {
-            return false;
-        }
-        return Objects.equals(group, ((Group) o).group);
+        return this == o || o != null && getClass().equals(o.getClass()) && Objects.equals(group, ((Group) o).group);
     }
 
     /**
@@ -84,6 +168,22 @@ public final class Group {
      */
     @Override
     public int hashCode() {
-        return Objects.hashCode(group);
+        return group.hashCode();
+    }
+
+    @Override
+    public Set<Group> getGroups() {
+        return Collections.singleton(this);
+    }
+
+    @Override
+    public boolean applyTo(Predicate<GroupStrategy> operation) {
+        return operation.test(this);
+    }
+
+    @Override
+    public GroupStrategy redefining(Map<Group, ? extends GroupStrategy> redefinitions) {
+        final GroupStrategy redefined = redefinitions.get(this);
+        return redefined == null ? this : redefined;
     }
 }

http://git-wip-us.apache.org/repos/asf/bval/blob/657def16/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupStrategy.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupStrategy.java b/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupStrategy.java
new file mode 100644
index 0000000..0b53a26
--- /dev/null
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/groups/GroupStrategy.java
@@ -0,0 +1,241 @@
+/*
+ * 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.groups;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+import org.apache.bval.jsr.util.ToUnmodifiable;
+import org.apache.bval.util.Validate;
+
+/**
+ * Group strategy interface.
+ */
+public interface GroupStrategy {
+    public static class Simple implements GroupStrategy {
+        private final Set<Group> groups;
+
+        private Simple(Set<Group> groups) {
+            this.groups = groups;
+        }
+
+        @Override
+        public Set<Group> getGroups() {
+            return groups;
+        }
+
+        @Override
+        public GroupStrategy redefining(Map<Group, ? extends GroupStrategy> redefinitions) {
+            if (Collections.disjoint(redefinitions.keySet(), groups)) {
+                return this;
+            }
+            return groups.stream().map(g -> redefinitions.containsKey(g) ? redefinitions.get(g) : g)
+                .collect(Collectors.collectingAndThen(Collectors.toList(), GroupStrategy::composite));
+        }
+
+        @Override
+        public int hashCode() {
+            return groups.hashCode();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            return obj == this
+                || obj != null && obj.getClass().equals(getClass()) && ((Simple) obj).groups.equals(groups);
+        }
+
+        @Override
+        public String toString() {
+            return groups.toString();
+        }
+    }
+
+    public static class Composite implements GroupStrategy {
+        private final Set<? extends GroupStrategy> components;
+        protected final boolean ordered;
+
+        public Composite(Collection<? extends GroupStrategy> components, boolean ordered) {
+            Validate.isTrue(Validate.notNull(components).stream().noneMatch(Objects::isNull),
+                "null component not permitted");
+            this.components = new LinkedHashSet<>(components);
+            this.ordered = ordered;
+        }
+
+        @Override
+        public Set<Group> getGroups() {
+            return components.stream().map(GroupStrategy::getGroups).flatMap(Collection::stream)
+                .collect(ToUnmodifiable.set());
+        }
+
+        @Override
+        public GroupStrategy redefining(Map<Group, ? extends GroupStrategy> redefinitions) {
+            if (!components.isEmpty()) {
+                final Set<GroupStrategy> redef =
+                    components.stream().map(cmp -> cmp.redefining(redefinitions)).collect(Collectors.toSet());
+                if (!redef.equals(components)) {
+                    return new Composite(redef, ordered);
+                }
+            }
+            return this;
+        }
+
+        @Override
+        public boolean applyTo(Predicate<GroupStrategy> operation) {
+            if (components.isEmpty()) {
+                return true;
+            }
+            final boolean applyAll = !ordered;
+            boolean result = true;
+            for (GroupStrategy gs : components) {
+                result = gs.applyTo(operation) && result;
+                if (!(applyAll || result)) {
+                    return false;
+                }
+            }
+            return result;
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(components, ordered);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null || !obj.getClass().equals(getClass())) {
+                return false;
+            }
+            final Composite other = (Composite) obj;
+            return other.components.equals(components) && other.ordered == ordered;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("%sordered: %s", ordered ? "" : "un", components);
+        }
+    }
+
+    public static final GroupStrategy EMPTY = new GroupStrategy() {
+
+        @Override
+        public GroupStrategy redefining(Map<Group, ? extends GroupStrategy> redefinitions) {
+            return this;
+        }
+
+        @Override
+        public Set<Group> getGroups() {
+            return Collections.emptySet();
+        }
+    };
+
+    public static GroupStrategy redefining(GroupStrategy source, Map<Group, ? extends GroupStrategy> redefinitions) {
+        Validate.notNull(source, "source");
+
+        if (!(redefinitions == null || redefinitions.isEmpty())) {
+            if (redefinitions.containsValue(null)) {
+                redefinitions = redefinitions.entrySet().stream().filter(e -> e.getValue() != null)
+                    .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+            }
+            if (!redefinitions.isEmpty()) {
+                return source.redefining(redefinitions);
+            }
+        }
+        return source;
+    }
+
+    public static GroupStrategy simple(Group... groups) {
+        return simple(Arrays.asList(groups));
+    }
+
+    public static GroupStrategy simple(Collection<? extends Group> coll) {
+        Validate.notNull(coll);
+        if (coll.size() == 1) {
+            return coll.iterator().next();
+        }
+        final Set<Group> groups = Collections.unmodifiableSet(new LinkedHashSet<>(coll));
+        return new Simple(groups);
+    }
+
+    public static GroupStrategy composite(GroupStrategy... components) {
+        return composite(Arrays.asList(components));
+    }
+
+    public static GroupStrategy composite(Collection<? extends GroupStrategy> components) {
+        if (components.isEmpty()) {
+            return EMPTY;
+        }
+        if (components.size() == 1) {
+            return components.iterator().next();
+        }
+        final Set<GroupStrategy> compressedComponents = new LinkedHashSet<>();
+
+        final Consumer<Set<Group>> addGroups = s -> {
+            if (!s.isEmpty()) {
+                compressedComponents.add(simple(s));
+                s.clear();
+            }
+        };
+        final Set<Group> unorderedGroups = new HashSet<>();
+        for (GroupStrategy component : components) {
+            if (component instanceof Composite && ((Composite) component).ordered) {
+                addGroups.accept(unorderedGroups);
+                compressedComponents.add(component);
+                continue;
+            }
+            unorderedGroups.addAll(component.getGroups());
+        }
+        addGroups.accept(unorderedGroups);
+        if (compressedComponents.size() == 1) {
+            return compressedComponents.iterator().next();
+        }
+        return new Composite(compressedComponents, false);
+    }
+
+    /**
+     * Get the associated groups.
+     * @return {@link Set} of {@link Group}
+     */
+    Set<Group> getGroups();
+
+    /**
+     * Get an equivalent strategy making group substitutions specified by {@code redefinitions}.
+     * @param redefinitions
+     * @return {@link GroupStrategy}
+     */
+    GroupStrategy redefining(Map<Group, ? extends GroupStrategy> redefinitions);
+
+    /**
+     * Apply the specified {@code boolean}-returning {@code operation}.
+     * @param operation
+     * @return {@code boolean}
+     */
+    default boolean applyTo(Predicate<GroupStrategy> operation) {
+        return operation.test(this);
+    }
+}

http://git-wip-us.apache.org/repos/asf/bval/blob/657def16/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 990cdaa..e51c47f 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
@@ -19,42 +19,42 @@
 package org.apache.bval.jsr.groups;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Set;
+import java.util.function.Consumer;
 
 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. */
-    private final List<Group> groups = new ArrayList<>();
-
-    /** The list of sequences. */
-    private final List<List<Group>> sequences = new ArrayList<>();
+    private final Set<Group> groups = new LinkedHashSet<>();
+    private final Set<Group.Sequence> sequences = new LinkedHashSet<>();
 
     /**
      * Get the Groups.
      * 
-     * @return {@link List} of {@link Group}.
+     * @return {@link Set} of {@link Group}.
      */
-    public List<Group> getGroups() {
-        return Collections.unmodifiableList(groups);
+    public Set<Group> getGroups() {
+        return Collections.unmodifiableSet(groups);
     }
 
     /**
      * Get the Group sequences.
      * 
-     * @return {@link List} of {@link List} of {@link Group}
+     * @return {@link List} of {@link Group.Sequence}
      */
-    public List<List<Group>> getSequences() {
-        return Collections.unmodifiableList(sequences);
+    public Collection<Group.Sequence> getSequences() {
+        return Collections.unmodifiableSet(sequences);
     }
 
     /**
@@ -62,11 +62,10 @@ public class Groups {
      * 
      * @param group
      *            to insert
+     * @return success
      */
-    void insertGroup(Group group) {
-        if (!groups.contains(group)) {
-            groups.add(group);
-        }
+    boolean insertGroup(Group group) {
+        return groups.add(group);
     }
 
     /**
@@ -74,26 +73,26 @@ public class Groups {
      * 
      * @param groups
      *            {@link List} of {@link Group} to insert
+     * @return success
      */
-    void insertSequence(List<Group> groups) {
-        if (!(groups == null || groups.isEmpty() || sequences.contains(groups))) {
-            sequences.add(Collections.unmodifiableList(groups));
-        }
+    boolean insertSequence(Collection<Group> groups) {
+        return !(groups == null || groups.isEmpty()) && sequences.add(Group.sequence(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
      */
+    @Deprecated
     public void assertDefaultGroupSequenceIsExpandable(List<Group> defaultGroups) {
-        for (List<Group> groupList : sequences) {
+        Consumer<List<Group>> action = (groupList) -> {
             final int idx = groupList.indexOf(Group.DEFAULT);
             if (idx >= 0) {
                 ensureExpandable(groupList, defaultGroups, idx);
             }
-        }
+        };
+        sequences.stream().map(Group.Sequence::getGroups).map(ArrayList::new).forEach(action);
     }
 
     private void ensureExpandable(List<Group> groupList, List<Group> defaultGroupList, int defaultGroupIndex) {
@@ -119,4 +118,11 @@ public class Groups {
                 defaultGroupList, groupList);
         }
     }
+
+    public GroupStrategy asStrategy() {
+        final List<GroupStrategy> components = new ArrayList<>();
+        components.addAll(groups);
+        components.addAll(sequences);
+        return GroupStrategy.composite(components);
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/bval/blob/657def16/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 3a895d4..b6b7a3a 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
@@ -81,6 +81,7 @@ public class GroupsComputer {
      * @param group
      * @return {@link Groups}
      */
+    @Deprecated
     public final Groups computeCascadingGroups(Set<GroupConversionDescriptor> groupConversions, Class<?> group) {
         final Groups preliminaryResult = computeGroups(Stream.of(group));
 
@@ -97,21 +98,21 @@ public class GroupsComputer {
 
         if (simpleGroup) {
             // ignore group inheritance from initial argument as that is handled elsewhere:
-            result.insertGroup(preliminaryResult.getGroups().get(0));
+            result.insertGroup(preliminaryResult.getGroups().iterator().next());
         } else {
             // expand group sequence conversions in place:
 
-            for (List<Group> seq : preliminaryResult.getSequences()) {
+            for (Group.Sequence seq : preliminaryResult.getSequences()) {
                 final List<Group> converted = new ArrayList<>();
-                for (Group gg : seq) {
+                for (Group gg : seq.getGroups()) {
                     final Class<?> c = gg.getGroup();
                     if (gcMap.containsKey(c)) {
                         final Groups convertedGroupExpansion = computeGroups(Stream.of(gcMap.get(c)));
                         if (convertedGroupExpansion.getSequences().isEmpty()) {
                             converted.add(gg);
                         } else {
-                            convertedGroupExpansion.getSequences().stream().flatMap(Collection::stream)
-                                .forEach(converted::add);
+                            convertedGroupExpansion.getSequences().stream().map(Group.Sequence::getGroups)
+                                .flatMap(Collection::stream).forEach(converted::add);
                         }
                     }
                 }

http://git-wip-us.apache.org/repos/asf/bval/blob/657def16/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
index af511f9..eef57f5 100644
--- 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
@@ -22,6 +22,7 @@ import java.lang.reflect.Constructor;
 import java.lang.reflect.Executable;
 import java.lang.reflect.Method;
 import java.lang.reflect.Type;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -39,6 +40,8 @@ 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.groups.Group;
+import org.apache.bval.jsr.groups.GroupStrategy;
 import org.apache.bval.jsr.metadata.Meta;
 import org.apache.bval.jsr.util.NodeImpl;
 import org.apache.bval.jsr.util.PathImpl;
@@ -127,14 +130,16 @@ public abstract class ValidateParameters<E extends Executable, T> extends Valida
         }
 
         @Override
-        void process(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
+        void process(GroupStrategy groups, Consumer<ConstraintViolation<T>> sink) {
             Validate.notNull(sink, "sink");
             final Lazy<Set<Frame<?>>> parameterFrames = new Lazy<>(this::parameterFrames);
-            each(expand(group), (t, u) -> {
-                validateDescriptorConstraints(t, u);
-                parameterFrames.get().forEach(p -> p.validateDescriptorConstraints(t, u));
-            }, sink);
-            parameterFrames.get().forEach(p -> p.recurse(group, sink));
+
+            GroupStrategy.redefining(groups, Collections.singletonMap(Group.DEFAULT, descriptor.getGroupStrategy()))
+                .applyTo(noViolations(gs -> {
+                    validateDescriptorConstraints(gs, sink);
+                    parameterFrames.get().forEach(p -> p.validateDescriptorConstraints(gs, sink));
+                }));
+            parameterFrames.get().forEach(p -> p.recurse(groups, sink));
         }
 
         @Override

http://git-wip-us.apache.org/repos/asf/bval/blob/657def16/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateProperty.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateProperty.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateProperty.java
index b87f98c..a489d1c 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateProperty.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidateProperty.java
@@ -52,6 +52,7 @@ import org.apache.bval.jsr.descriptor.ContainerElementTypeD;
 import org.apache.bval.jsr.descriptor.DescriptorManager;
 import org.apache.bval.jsr.descriptor.ElementD;
 import org.apache.bval.jsr.descriptor.PropertyD;
+import org.apache.bval.jsr.groups.GroupStrategy;
 import org.apache.bval.jsr.metadata.ContainerElementKey;
 import org.apache.bval.jsr.util.PathImpl;
 import org.apache.bval.jsr.util.PathNavigation;
@@ -457,9 +458,9 @@ public final class ValidateProperty<T> extends ValidationJob<T> {
         }
 
         @Override
-        void recurse(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
+        void recurse(GroupStrategy groups, Consumer<ConstraintViolation<T>> sink) {
             if (cascade) {
-                super.recurse(group, sink);
+                super.recurse(groups, sink);
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/bval/blob/657def16/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java
index 839c14c..b86a79d 100644
--- a/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java
+++ b/bval-jsr/src/main/java/org/apache/bval/jsr/job/ValidationJob.java
@@ -22,6 +22,7 @@ import java.lang.reflect.Array;
 import java.lang.reflect.Type;
 import java.lang.reflect.TypeVariable;
 import java.util.Collections;
+import java.util.IdentityHashMap;
 import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
@@ -29,9 +30,9 @@ import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.ConcurrentSkipListSet;
-import java.util.function.BiConsumer;
+import java.util.concurrent.ConcurrentSkipListMap;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import java.util.stream.Stream;
@@ -59,9 +60,11 @@ import org.apache.bval.jsr.descriptor.BeanD;
 import org.apache.bval.jsr.descriptor.ComposedD;
 import org.apache.bval.jsr.descriptor.ConstraintD;
 import org.apache.bval.jsr.descriptor.ContainerElementTypeD;
+import org.apache.bval.jsr.descriptor.DescriptorManager;
 import org.apache.bval.jsr.descriptor.ElementD;
 import org.apache.bval.jsr.descriptor.PropertyD;
 import org.apache.bval.jsr.groups.Group;
+import org.apache.bval.jsr.groups.GroupStrategy;
 import org.apache.bval.jsr.groups.Groups;
 import org.apache.bval.jsr.metadata.ContainerElementKey;
 import org.apache.bval.jsr.util.NodeImpl;
@@ -97,28 +100,31 @@ public abstract class ValidationJob<T> {
             return ValidationJob.this;
         }
 
-        void process(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
+        void process(GroupStrategy groups, Consumer<ConstraintViolation<T>> sink) {
             Validate.notNull(sink, "sink");
-            each(expand(group), this::validateDescriptorConstraints, sink);
-            recurse(group, sink);
+
+            GroupStrategy.redefining(groups, Collections.singletonMap(Group.DEFAULT, descriptor.getGroupStrategy()))
+                .applyTo(noViolations(gs -> validateDescriptorConstraints(gs, sink)));
+
+            recurse(groups, sink);
         }
 
-        void recurse(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
+        void recurse(GroupStrategy groups, Consumer<ConstraintViolation<T>> sink) {
             throw new UnsupportedOperationException();
         }
 
         abstract Object getBean();
 
-        void validateDescriptorConstraints(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
-            constraintsFor(descriptor, group)
+        void validateDescriptorConstraints(GroupStrategy groups, Consumer<ConstraintViolation<T>> sink) {
+            constraintsFor(descriptor, groups)
                 .forEach(c -> unwrap(c.getValueUnwrapping()).forEach(f -> f.validate(c, sink)));
         }
 
         private Stream<Frame<D>> unwrap(ValidateUnwrappedValue valueUnwrapping) {
             if (valueUnwrapping != ValidateUnwrappedValue.SKIP && context.getValue() != null) {
                 final Optional<ValueExtractors.UnwrappingInfo> valueExtractorAndAssociatedContainerElementKey =
-                        validatorContext.getValueExtractors().
-                    findUnwrappingInfo(context.getValue().getClass(), valueUnwrapping);
+                    validatorContext.getValueExtractors().findUnwrappingInfo(context.getValue().getClass(),
+                        valueUnwrapping);
 
                 if (valueExtractorAndAssociatedContainerElementKey.isPresent()) {
                     return ExtractValues
@@ -132,14 +138,14 @@ public abstract class ValidationJob<T> {
 
         @SuppressWarnings({ "rawtypes", "unchecked" })
         private boolean validate(ConstraintD<?> constraint, Consumer<ConstraintViolation<T>> sink) {
-            if (!validatedPathsByConstraint
-                .computeIfAbsent(constraint, k -> new ConcurrentSkipListSet<>(PathImpl.PATH_COMPARATOR))
-                .add(context.getPath())) {
-                // seen, ignore:
+            final ConcurrentMap<Path, Set<Object>> pathMap = completedValidations.computeIfAbsent(constraint,
+                k -> new ConcurrentSkipListMap<>(PathImpl.PATH_COMPARATOR));
+            final Set<Object> objectSet =
+                pathMap.computeIfAbsent(context.getPath(), p -> Collections.newSetFromMap(new IdentityHashMap<>()));
+            if (!objectSet.add(context.getValue())) {
                 return true;
             }
             final ConstraintValidator constraintValidator = getConstraintValidator(constraint);
-
             final ConstraintValidatorContextImpl<T> constraintValidatorContext =
                 new ConstraintValidatorContextImpl<>(this, constraint);
 
@@ -181,10 +187,10 @@ public abstract class ValidationJob<T> {
             } : sink;
 
             // collect validation results to set of Boolean, ensuring all are evaluated:
-            final Set<Boolean> results = constraint.getComposingConstraints().stream().map(ConstraintD.class::cast)
+            final Set<Boolean> validationResults = constraint.getComposingConstraints().stream().map(ConstraintD.class::cast)
                 .map(c -> validate(c, effectiveSink)).collect(Collectors.toSet());
 
-            return Collections.singleton(Boolean.TRUE).equals(results);
+            return Collections.singleton(Boolean.TRUE).equals(validationResults);
         }
 
         @SuppressWarnings({ "rawtypes" })
@@ -227,18 +233,6 @@ public abstract class ValidationJob<T> {
 
             return extractedType.orElse(elementClass);
         }
-
-        Stream<Class<?>> expand(Class<?> group) {
-            if (Default.class.equals(group)) {
-                final List<Class<?>> groupSequence = descriptor.getGroupSequence();
-                if (groupSequence != null) {
-                    groups.assertDefaultGroupSequenceIsExpandable(
-                        groupSequence.stream().map(Group::new).collect(Collectors.toList()));
-                    return groupSequence.stream();
-                }
-            }
-            return Stream.of(group);
-        }
     }
 
     public class BeanFrame<B> extends Frame<BeanD<B>> {
@@ -254,14 +248,25 @@ public abstract class ValidationJob<T> {
             this.realContext = context;
         }
 
-        void process(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
+        void process(GroupStrategy groups, Consumer<ConstraintViolation<T>> sink) {
             Validate.notNull(sink, "sink");
             final Lazy<Set<Frame<?>>> propertyFrames = new Lazy<>(this::propertyFrames);
-            each(expand(group), (t, u) -> {
-                validateDescriptorConstraints(t, u);
-                propertyFrames.get().forEach(p -> p.validateDescriptorConstraints(t, u));
-            }, sink);
-            propertyFrames.get().forEach(p -> p.recurse(group, sink));
+
+            final GroupStrategy localGroupStrategy = GroupStrategy.redefining(groups,
+                Collections.singletonMap(Group.DEFAULT, descriptor.getGroupStrategy()));
+
+            localGroupStrategy.applyTo(noViolations(gs -> {
+                validateDescriptorConstraints(gs, sink);
+                propertyFrames.get().forEach(p -> {
+                    p.validateDescriptorConstraints(gs, sink);
+                    if (localGroupStrategy == groups) {
+                        p.recurse(gs, sink);
+                    }
+                });
+            }));
+            if (localGroupStrategy != groups) {
+                propertyFrames.get().forEach(p -> p.recurse(groups, sink));
+            }
         }
 
         protected Frame<?> propertyFrame(PropertyD<?> d, GraphContext context) {
@@ -305,54 +310,48 @@ public abstract class ValidationJob<T> {
         public SproutFrame(Frame<?> parent, D descriptor, GraphContext context) {
             super(parent, descriptor, context);
         }
-        
+
         @Override
-        void validateDescriptorConstraints(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
-            super.validateDescriptorConstraints(group, sink);
+        void validateDescriptorConstraints(GroupStrategy groups, Consumer<ConstraintViolation<T>> sink) {
+            super.validateDescriptorConstraints(groups, sink);
             if (context.getValue() != null) {
                 descriptor.getConstrainedContainerElementTypes().stream()
                     .flatMap(d -> ComposedD.unwrap(d, ContainerElementTypeD.class)).forEach(d -> {
-                        if (constraintsFor(d, group).findFirst().isPresent()
+                        if (constraintsFor(d, groups).findFirst().isPresent()
                             || !d.getConstrainedContainerElementTypes().isEmpty()) {
                             final ValueExtractor<?> declaredTypeValueExtractor =
                                 context.getValidatorContext().getValueExtractors().find(d.getKey());
                             ExtractValues.extract(context, d.getKey(), declaredTypeValueExtractor).stream()
                                 .filter(e -> !e.isRecursive())
                                 .map(e -> new ContainerElementConstraintsFrame(this, d, e))
-                                .forEach(f -> f.validateDescriptorConstraints(group, sink));
+                                .forEach(f -> f.validateDescriptorConstraints(groups, sink));
                         }
                     });
             }
         }
 
         @Override
-        void recurse(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
-            final Groups convertedGroups =
-                validatorContext.getGroupsComputer().computeCascadingGroups(descriptor.getGroupConversions(),
-                    descriptor.getDeclaringClass().isAssignableFrom(group) ? Default.class : group);
-
-            convertedGroups.getGroups().stream().map(Group::getGroup).forEach(g -> cascade(g, sink));
-
-            sequences: for (List<Group> seq : convertedGroups.getSequences()) {
-                final boolean proceed = each(seq.stream().map(Group::getGroup), this::cascade, sink);
-                if (!proceed) {
-                    break sequences;
-                }
-            }
-        }
-
-        private void cascade(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
-            if (context.getValue() != null) {
-                descriptor.getConstrainedContainerElementTypes().stream()
-                    .filter(d -> d.isCascaded() || !d.getConstrainedContainerElementTypes().isEmpty())
-                    .flatMap(d -> ComposedD.unwrap(d, ContainerElementTypeD.class)).forEach(d -> {
-                        final ValueExtractor<?> runtimeTypeValueExtractor =
-                            context.getValidatorContext().getValueExtractors().find(context.runtimeKey(d.getKey()));
-                        ExtractValues.extract(context, d.getKey(), runtimeTypeValueExtractor).stream()
-                            .filter(e -> !e.isRecursive()).map(e -> new ContainerElementCascadeFrame(this, d, e))
-                            .forEach(f -> f.recurse(group, sink));
-                    });
+        void recurse(GroupStrategy groups, Consumer<ConstraintViolation<T>> sink) {
+            if (context.getValue() == null || !DescriptorManager.isCascaded(descriptor)) {
+                return;
             }
+            final Map<Group, GroupStrategy> conversions =
+                descriptor.getGroupConversions().stream().collect(Collectors.toMap(gc -> Group.of(gc.getFrom()),
+                    gc -> validatorContext.getGroupsComputer().computeGroups(gc.getTo()).asStrategy()));
+
+            GroupStrategy.redefining(groups, conversions).applyTo(noViolations(gs -> cascade(gs, sink)));
+        }
+
+        private void cascade(GroupStrategy groups, Consumer<ConstraintViolation<T>> sink) {
+            descriptor.getConstrainedContainerElementTypes().stream()
+                .filter(d -> d.isCascaded() || !d.getConstrainedContainerElementTypes().isEmpty())
+                .flatMap(d -> ComposedD.unwrap(d, ContainerElementTypeD.class)).forEach(d -> {
+                    final ValueExtractor<?> runtimeTypeValueExtractor =
+                        context.getValidatorContext().getValueExtractors().find(context.runtimeKey(d.getKey()));
+                    ExtractValues.extract(context, d.getKey(), runtimeTypeValueExtractor).stream()
+                        .filter(e -> !e.isRecursive()).map(e -> new ContainerElementCascadeFrame(this, d, e))
+                        .forEach(f -> f.recurse(groups, sink));
+                });
             if (!descriptor.isCascaded()) {
                 return;
             }
@@ -377,7 +376,7 @@ public abstract class ValidationJob<T> {
                 }
             }
             multiplex().filter(context -> context.getValue() != null && !context.isRecursive())
-                .map(context -> new BeanFrame<>(this, context)).forEach(b -> b.process(group, sink));
+                .map(context -> new BeanFrame<>(this, context)).forEach(b -> b.process(groups, sink));
         }
 
         protected GraphContext getMultiplexContext() {
@@ -450,9 +449,9 @@ public abstract class ValidationJob<T> {
             GraphContext context) {
             super(parent, descriptor, context);
         }
-    
+
         @Override
-        void recurse(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
+        void recurse(GroupStrategy groups, Consumer<ConstraintViolation<T>> sink) {
         }
     }
 
@@ -464,7 +463,7 @@ public abstract class ValidationJob<T> {
         }
 
         @Override
-        void validateDescriptorConstraints(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
+        void validateDescriptorConstraints(GroupStrategy groups, Consumer<ConstraintViolation<T>> sink) {
         }
 
         @Override
@@ -472,19 +471,19 @@ public abstract class ValidationJob<T> {
             final PathImpl path = context.getPath();
 
             GraphContext ancestor = context.getParent();
-            Validate.validState(ancestor!= null, "Expected parent context");
+            Validate.validState(ancestor != null, "Expected parent context");
 
             final NodeImpl leafNode = path.getLeafNode();
-            
+
             final NodeImpl newLeaf;
-            
+
             if (leafNode.getKind() == ElementKind.CONTAINER_ELEMENT) {
                 // recurse using elided path:
                 path.removeLeafNode();
 
                 while (!path.equals(ancestor.getPath())) {
                     ancestor = ancestor.getParent();
-                    Validate.validState(ancestor!= null, "Expected parent context");
+                    Validate.validState(ancestor != null, "Expected parent context");
                 }
                 newLeaf = new NodeImpl.PropertyNodeImpl(leafNode);
                 newLeaf.setName(null);
@@ -508,12 +507,12 @@ public abstract class ValidationJob<T> {
         }
 
         @Override
-        void validateDescriptorConstraints(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
+        void validateDescriptorConstraints(GroupStrategy groups, Consumer<ConstraintViolation<T>> sink) {
             throw exc.get();
         }
 
         @Override
-        void recurse(Class<?> group, Consumer<ConstraintViolation<T>> sink) {
+        void recurse(GroupStrategy groups, Consumer<ConstraintViolation<T>> sink) {
             throw exc.get();
         }
 
@@ -526,21 +525,21 @@ public abstract class ValidationJob<T> {
     protected static final TypeVariable<?> MAP_VALUE = Map.class.getTypeParameters()[1];
     protected static final TypeVariable<?> ITERABLE_ELEMENT = Iterable.class.getTypeParameters()[0];
 
-    private static Stream<ConstraintD<?>> constraintsFor(ElementD<?, ?> descriptor, Class<?> group) {
+    private static Stream<ConstraintD<?>> constraintsFor(ElementD<?, ?> descriptor, GroupStrategy groups) {
         return descriptor.getConstraintDescriptors().stream().<ConstraintD<?>> map(ConstraintD.class::cast)
             .filter(c -> {
                 final Set<Class<?>> constraintGroups = c.getGroups();
-                return constraintGroups.contains(group)
-                    || constraintGroups.contains(Default.class) && c.getDeclaringClass().isAssignableFrom(group);
+                return groups.getGroups().stream().map(Group::getGroup).anyMatch(g -> constraintGroups.contains(g)
+                    || constraintGroups.contains(Default.class) && c.getDeclaringClass().equals(g));
             });
     }
 
     protected final ApacheFactoryContext validatorContext;
+    protected final Groups groups;
 
-    private final Groups groups;
     private final Lazy<Set<ConstraintViolation<T>>> results = new Lazy<>(LinkedHashSet::new);
 
-    private ConcurrentMap<ConstraintD<?>, Set<Path>> validatedPathsByConstraint;
+    private ConcurrentMap<ConstraintD<?>, ConcurrentMap<Path, Set<Object>>> completedValidations;
 
     ValidationJob(ApacheFactoryContext validatorContext, Class<?>[] groups) {
         super();
@@ -558,19 +557,12 @@ public abstract class ValidationJob<T> {
 
             final Consumer<ConstraintViolation<T>> sink = results.consumer(Set::add);
 
-            validatedPathsByConstraint = new ConcurrentHashMap<>();
+            completedValidations = new ConcurrentHashMap<>();
 
             try {
-                groups.getGroups().stream().map(Group::getGroup).forEach(g -> baseFrame.process(g, sink));
-
-                sequences: for (List<Group> seq : groups.getSequences()) {
-                    final boolean proceed = each(seq.stream().map(Group::getGroup), baseFrame::process, sink);
-                    if (!proceed) {
-                        break sequences;
-                    }
-                }
+                baseFrame.process(groups.asStrategy(), sink);
             } finally {
-                validatedPathsByConstraint = null;
+                completedValidations = null;
             }
             if (results.optional().isPresent()) {
                 return Collections.unmodifiableSet(results.get());
@@ -579,20 +571,6 @@ public abstract class ValidationJob<T> {
         return results.reset(Collections::emptySet).get();
     }
 
-    protected boolean each(Stream<Class<?>> groupSequence,
-        BiConsumer<Class<?>, Consumer<ConstraintViolation<T>>> closure, Consumer<ConstraintViolation<T>> sink) {
-        final Lazy<Set<ConstraintViolation<T>>> sequenceViolations = new Lazy<>(LinkedHashSet::new);
-        final Consumer<ConstraintViolation<T>> addSequenceViolation = sequenceViolations.consumer(Set::add);
-        for (Class<?> g : (Iterable<Class<?>>) groupSequence::iterator) {
-            closure.accept(g, addSequenceViolation);
-            if (sequenceViolations.optional().isPresent()) {
-                sequenceViolations.get().forEach(sink);
-                return false;
-            }
-        }
-        return true;
-    }
-
     @SuppressWarnings("unchecked")
     private <O> BeanD<O> getBeanDescriptor(Object bean) {
         final Class<? extends Object> t = Proxies.classFor(Validate.notNull(bean, "bean").getClass());
@@ -621,6 +599,19 @@ public abstract class ValidationJob<T> {
         return true;
     }
 
+    protected <U> Predicate<U> noViolations(Consumer<? super U> consumer) {
+        return u -> {
+            final int originalCount = violationCount();
+            consumer.accept(u);
+            return violationCount() == originalCount;
+        };
+    }
+
+    private int violationCount() {
+        final Optional<Set<ConstraintViolation<T>>> maybeResults = results.optional();
+        return maybeResults.isPresent() ? maybeResults.get().size() : 0;
+    }
+
     private final String interpolate(String messageTemplate, MessageInterpolator.Context context) {
         try {
             return validatorContext.getMessageInterpolator().interpolate(messageTemplate, context);

http://git-wip-us.apache.org/repos/asf/bval/blob/657def16/bval-jsr/src/test/java/org/apache/bval/jsr/groups/GroupSequenceTest.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/test/java/org/apache/bval/jsr/groups/GroupSequenceTest.java b/bval-jsr/src/test/java/org/apache/bval/jsr/groups/GroupSequenceTest.java
index 85e969a..cb30598 100644
--- a/bval-jsr/src/test/java/org/apache/bval/jsr/groups/GroupSequenceTest.java
+++ b/bval-jsr/src/test/java/org/apache/bval/jsr/groups/GroupSequenceTest.java
@@ -20,13 +20,15 @@ package org.apache.bval.jsr.groups;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
 
+import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
+import java.util.List;
 import java.util.Set;
+import java.util.function.Predicate;
 
 import javax.validation.ConstraintViolation;
+import javax.validation.GroupDefinitionException;
 import javax.validation.GroupSequence;
 import javax.validation.constraints.NotNull;
 
@@ -45,13 +47,9 @@ import org.junit.Test;
  * Description: test of group sequence behavior<br/>
  */
 public class GroupSequenceTest extends ValidationTestBase {
-
-    @Test
+    @Test(expected = GroupDefinitionException.class)
     public void testGroupSequence1() {
-        BeanD<?> bean = (BeanD<?>) ApacheValidatorFactory.getDefault().usingContext().getValidator()
-            .getConstraintsForClass(GInterface1.class);
-
-        assertEquals(Collections.singletonList(GInterface1.class), bean.getGroupSequence());
+        ApacheValidatorFactory.getDefault().usingContext().getValidator().getConstraintsForClass(GInterface1.class);
     }
 
     @Test
@@ -59,23 +57,40 @@ public class GroupSequenceTest extends ValidationTestBase {
         BeanD<?> bean = (BeanD<?>) ApacheValidatorFactory.getDefault().usingContext().getValidator()
             .getConstraintsForClass(GClass1.class);
 
-        assertNull(bean.getGroupSequence());
+        assertEquals(Group.of(GClass1.class), bean.getGroupStrategy());
     }
 
     @Test
     public void testGroupSequence3() {
         BeanD<?> bean = (BeanD<?>) ApacheValidatorFactory.getDefault().usingContext().getValidator()
-                .getConstraintsForClass(GClass2.class);
+            .getConstraintsForClass(GClass2.class);
+
+        class TestPredicate implements Predicate<GroupStrategy> {
+
+            final List<GroupStrategy> strategies = new ArrayList<>();
 
-        assertEquals(Arrays.asList(GClass1.class, GClass2.class), bean.getGroupSequence());
+            @Override
+            public boolean test(GroupStrategy t) {
+                return strategies.add(t);
+            }
+        }
+
+        final TestPredicate p = new TestPredicate();
+
+        bean.getGroupStrategy().applyTo(p);
+
+        Group g1 = Group.of(GClass1.class);
+        Group g2 = Group.of(GClass2.class);
+
+        assertEquals(Arrays.asList(g1, GroupStrategy.simple(g1, g2)), p.strategies);
     }
 
     @Test
     public void testGroupSequence4() {
         BeanD<?> bean = (BeanD<?>) ApacheValidatorFactory.getDefault().usingContext().getValidator()
-                .getConstraintsForClass(GClass3.class);
+            .getConstraintsForClass(GClass3.class);
 
-        assertEquals(Arrays.asList(GClass3.class, GClass1.class), bean.getGroupSequence());
+        assertEquals(Group.sequence(Group.of(GClass3.class), Group.of(GClass1.class)), bean.getGroupStrategy());
     }
 
     @Test
@@ -188,5 +203,4 @@ public class GroupSequenceTest extends ValidationTestBase {
         interface Group1 {
         }
     }
-
 }

http://git-wip-us.apache.org/repos/asf/bval/blob/657def16/bval-jsr/src/test/java/org/apache/bval/jsr/groups/GroupsComputerTest.java
----------------------------------------------------------------------
diff --git a/bval-jsr/src/test/java/org/apache/bval/jsr/groups/GroupsComputerTest.java b/bval-jsr/src/test/java/org/apache/bval/jsr/groups/GroupsComputerTest.java
index 092d527..62fb18a 100644
--- a/bval-jsr/src/test/java/org/apache/bval/jsr/groups/GroupsComputerTest.java
+++ b/bval-jsr/src/test/java/org/apache/bval/jsr/groups/GroupsComputerTest.java
@@ -23,7 +23,6 @@ import static org.junit.Assert.assertEquals;
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Set;
 
 import javax.validation.GroupDefinitionException;
@@ -64,7 +63,7 @@ public class GroupsComputerTest {
 
     @Test
     public void testGroupChainForEmptySet() {
-        assertEquals(Collections.singletonList(Group.DEFAULT),
+        assertEquals(Collections.singleton(Group.DEFAULT),
             groupsComputer.computeGroups(new HashSet<Class<?>>()).getGroups());
     }
 
@@ -106,10 +105,10 @@ public class GroupsComputerTest {
         Set<Class<?>> groups = new HashSet<Class<?>>();
         groups.add(Address.Complete.class);
         Groups chain = groupsComputer.computeGroups(groups);
-        Iterator<List<Group>> sequences = chain.getSequences().iterator();
-        List<Group> sequence = sequences.next();
+        Iterator<Group.Sequence> sequences = chain.getSequences().iterator();
+        Iterator<Group> sequence = sequences.next().getGroups().iterator();
 
-        assertEquals(Default.class, sequence.get(0).getGroup());
-        assertEquals(Address.HighLevelCoherence.class, sequence.get(1).getGroup());
+        assertEquals(Default.class, sequence.next().getGroup());
+        assertEquals(Address.HighLevelCoherence.class, sequence.next().getGroup());
     }
 }


[2/2] bval git commit: Merge remote-tracking branch 'origin/bv2' into bv2

Posted by mb...@apache.org.
Merge remote-tracking branch 'origin/bv2' into bv2


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

Branch: refs/heads/bv2
Commit: f5bdeaf102d148e94c41dae14348d6505a7e6a80
Parents: 657def1 d1ddb57
Author: Matt Benson <mb...@apache.org>
Authored: Thu Apr 12 13:01:19 2018 -0500
Committer: Matt Benson <mb...@apache.org>
Committed: Thu Apr 12 13:01:19 2018 -0500

----------------------------------------------------------------------
 pom.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------