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/395f6df5 Tree: http://git-wip-us.apache.org/repos/asf/bval/tree/395f6df5 Diff: http://git-wip-us.apache.org/repos/asf/bval/diff/395f6df5 Branch: refs/heads/bv2 Commit: 395f6df566c3376dff4aa83aea01e6ad57588024 Parents: ecb4f74 Author: Matt Benson <[email protected]> Authored: Thu Apr 12 13:00:58 2018 -0500 Committer: Matt Benson <[email protected]> Committed: Tue Oct 16 12:28:20 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/395f6df5/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/395f6df5/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/395f6df5/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/395f6df5/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/395f6df5/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/395f6df5/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/395f6df5/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/395f6df5/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/395f6df5/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/395f6df5/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/395f6df5/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/395f6df5/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/395f6df5/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/395f6df5/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/395f6df5/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/395f6df5/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()); } }
