/* * Copyright 2012 Google Inc. Copyright 2016 Manfred Tremmel * * Licensed 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 de.knightsoftnet.validators.client.impl; import de.knightsoftnet.validators.client.impl.metadata.ValidationGroupsMetadata; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.validation.GroupDefinitionException; import javax.validation.ValidationException; /** * Helper class used to resolve groups and sequences into a single chain of groups which can then be * validated. * <p> * Modified from the Hibernate validator for use with GWT. * </p> */ public class GroupChainGenerator { private final ValidationGroupsMetadata validationGroupsMetadata; private final Map<Class<?>, List<Group>> resolvedSequences = new HashMap<Class<?>, List<Group>>(); public GroupChainGenerator(final ValidationGroupsMetadata validationGroupsMetadata) { this.validationGroupsMetadata = validationGroupsMetadata; } /** * Generates a chain of groups to be validated given the specified validation groups. * * @param groups The groups specified at the validation call. * * @return an instance of {@code GroupChain} defining the order in which validation has to occur. */ public GroupChain getGroupChainFor(final Collection<Class<?>> groups) { if (groups == null || groups.isEmpty()) { throw new IllegalArgumentException("At least one group has to be specified."); } for (final Class<?> clazz : groups) { if (!this.validationGroupsMetadata.containsGroup(clazz) && !this.validationGroupsMetadata.isSeqeuence(clazz)) { throw new ValidationException("The class " + clazz + " is not a valid group or sequence."); } } final GroupChain chain = new GroupChain(); for (final Class<?> clazz : groups) { if (this.isGroupSequence(clazz)) { this.insertSequence(clazz, chain); } else { final Group group = new Group(clazz); chain.insertGroup(group); this.insertInheritedGroups(clazz, chain); } } return chain; } @Override public String toString() { final StringBuilder sb = new StringBuilder(64); sb.append("GroupChainGenerator"); sb.append("{resolvedSequences=").append(this.resolvedSequences); sb.append('}'); return sb.toString(); } private void addGroups(final List<Group> resolvedGroupSequence, final List<Group> groups) { for (final Group tmpGroup : groups) { if (resolvedGroupSequence.contains(tmpGroup) && resolvedGroupSequence.indexOf(tmpGroup) < resolvedGroupSequence.size() - 1) { throw new GroupDefinitionException("Unable to expand group sequence."); } resolvedGroupSequence.add(tmpGroup); } } private void addInheritedGroups(final Group group, final List<Group> expandedGroups) { final Set<Class<?>> inheritedGroups = this.validationGroupsMetadata.getParentsOfGroup(group.getGroup()); if (inheritedGroups != null) { for (final Class<?> inheritedGroup : inheritedGroups) { if (this.isGroupSequence(inheritedGroup)) { throw new GroupDefinitionException( "Sequence definitions are not allowed as composing " + "parts of a sequence."); } final Group g = new Group(inheritedGroup, group.getSequence()); expandedGroups.add(g); this.addInheritedGroups(g, expandedGroups); } } } private List<Group> expandInhertitedGroups(final List<Group> sequence) { final List<Group> expandedGroup = new ArrayList<Group>(); for (final Group group : sequence) { expandedGroup.add(group); this.addInheritedGroups(group, expandedGroup); } return expandedGroup; } /** * Recursively add inherited groups into the group chain. * * @param clazz The group interface * @param chain The group chain we are currently building. */ private void insertInheritedGroups(final Class<?> clazz, final GroupChain chain) { for (final Class<?> inheritedGroup : this.validationGroupsMetadata.getParentsOfGroup(clazz)) { final Group group = new Group(inheritedGroup); chain.insertGroup(group); this.insertInheritedGroups(inheritedGroup, chain); } } private void insertSequence(final Class<?> clazz, final GroupChain chain) { List<Group> sequence; if (this.resolvedSequences.containsKey(clazz)) { sequence = this.resolvedSequences.get(clazz); } else { sequence = this.resolveSequence(clazz, new ArrayList<Class<?>>()); // we expand the inherited groups only after we determined whether the sequence is expandable sequence = this.expandInhertitedGroups(sequence); } chain.insertSequence(sequence); } private boolean isGroupSequence(final Class<?> clazz) { return this.validationGroupsMetadata.isSeqeuence(clazz); } private List<Group> resolveSequence(final Class<?> group, final List<Class<?>> processedSequences) { if (processedSequences.contains(group)) { throw new GroupDefinitionException("Cyclic dependency in groups definition"); } else { processedSequences.add(group); } final List<Group> resolvedGroupSequence = new ArrayList<Group>(); final List<Class<?>> sequenceList = this.validationGroupsMetadata.getSequenceList(group); for (final Class<?> clazz : sequenceList) { if (this.isGroupSequence(clazz)) { final List<Group> tmpSequence = this.resolveSequence(clazz, processedSequences); this.addGroups(resolvedGroupSequence, tmpSequence); } else { final List<Group> list = new ArrayList<Group>(); list.add(new Group(clazz, group)); this.addGroups(resolvedGroupSequence, list); } } this.resolvedSequences.put(group, resolvedGroupSequence); return resolvedGroupSequence; } }