| /* |
| * 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.jackrabbit.oak.plugins.nodetype; |
| |
| import javax.jcr.PropertyType; |
| import javax.jcr.nodetype.ItemDefinition; |
| import javax.jcr.nodetype.NodeDefinition; |
| import javax.jcr.nodetype.NodeTypeDefinition; |
| import javax.jcr.nodetype.PropertyDefinition; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| import org.apache.jackrabbit.JcrConstants; |
| |
| /** |
| * A <code>NodeTypeDefDiff</code> represents the result of the comparison of |
| * two node type definitions. |
| * <p> |
| * The result of the comparison can be categorized as one of the following types: |
| * <p> |
| * <b><code>NONE</code></b> indicates that there is no modification at all. |
| * <p> |
| * A <b><code>TRIVIAL</code></b> modification has no impact on the consistency |
| * of existing content. The following modifications are considered |
| * <code>TRIVIAL</code>: |
| * <ul> |
| * <li>changing node type <code>orderableChildNodes</code> flag |
| * <li>changing node type <code>primaryItemName</code> value |
| * <li>adding non-<code>mandatory</code> property/child node |
| * <li>changing property/child node <code>protected</code> flag |
| * <li>changing property/child node <code>onParentVersion</code> value |
| * <li>changing property/child node <code>mandatory</code> flag to <code>false</code> |
| * <li>changing property/child node <code>autoCreated</code> flag |
| * <li>changing specific property/child node <code>name</code> to <code>*</code> |
| * <li>changing child node <code>defaultPrimaryType</code> |
| * <li>changing child node <code>sameNameSiblings</code> flag to <code>true</code> |
| * <li>weaken child node <code>requiredPrimaryTypes</code> (e.g. by removing) |
| * <li>weaken property <code>valueConstraints</code> (e.g. by removing a constraint |
| * or by making a specific constraint less restrictive) |
| * <li>changing property <code>defaultValues</code> |
| * <li>changing specific property <code>requiredType</code> to <code>undefined</code> |
| * <li>changing property <code>multiple</code> flag to <code>true</code> |
| * </ul> |
| * <p> |
| * A <b><code>MAJOR</code></b> modification potentially <i>affects</i> the |
| * consistency of existing content. |
| * |
| * All modifications that are not <b><code>TRIVIAL</code></b> are considered |
| * <b><code>MAJOR</code></b>. |
| * |
| * <p> |
| * <em>This class duplicates code from org.apache.jackrabbit.spi.commons.nodetype.NodeTypeDefDiff; both should be updated in sync, |
| * see <a href="https://issues.apache.org/jira/browse/OAK-2802">OAK-2802</a></em> |
| */ |
| public class NodeTypeDefDiff { |
| |
| /** |
| * no modification |
| */ |
| public static final int NONE = 0; |
| /** |
| * trivial modification: does not affect consistency of existing content |
| */ |
| public static final int TRIVIAL = 1; |
| /** |
| * major modification: <i>does</i> affect consistency of existing content |
| */ |
| public static final int MAJOR = 2; |
| |
| private final NodeTypeDefinition oldDef; |
| private final NodeTypeDefinition newDef; |
| private int type; |
| |
| private final List<PropDefDiff> propDefDiffs = new ArrayList<PropDefDiff>(); |
| private final List<ChildNodeDefDiff> childNodeDefDiffs = new ArrayList<ChildNodeDefDiff>(); |
| |
| /** |
| * Constructor |
| * @param oldDef old definition |
| * @param newDef new definition |
| */ |
| private NodeTypeDefDiff(NodeTypeDefinition oldDef, NodeTypeDefinition newDef) { |
| this.oldDef = oldDef; |
| this.newDef = newDef; |
| init(); |
| } |
| |
| /** |
| * |
| */ |
| private void init() { |
| if (oldDef.equals(newDef)) { |
| // definitions are identical |
| type = NONE; |
| } else { |
| // definitions are not identical, determine type of modification |
| |
| // assume TRIVIAL change by default |
| type = TRIVIAL; |
| |
| // check supertypes |
| int tmpType = supertypesDiff(); |
| if (tmpType > type) { |
| type = tmpType; |
| } |
| |
| // check mixin flag (MAJOR modification) |
| tmpType = mixinFlagDiff(); |
| if (tmpType > type) { |
| type = tmpType; |
| } |
| |
| // check abstract flag (MAJOR modification) |
| tmpType = abstractFlagDiff(); |
| if (tmpType > type) { |
| type = tmpType; |
| } |
| |
| // no need to check orderableChildNodes flag (TRIVIAL modification) |
| // no need to check queryable flag (TRIVIAL modification) |
| |
| // check property definitions |
| tmpType = buildPropDefDiffs(); |
| if (tmpType > type) { |
| type = tmpType; |
| } |
| |
| // check child node definitions |
| tmpType = buildChildNodeDefDiffs(); |
| if (tmpType > type) { |
| type = tmpType; |
| } |
| } |
| } |
| |
| /** |
| * @param oldDef old definition |
| * @param newDef new definition |
| * @return the diff |
| */ |
| public static NodeTypeDefDiff create(NodeTypeDefinition oldDef, NodeTypeDefinition newDef) { |
| if (oldDef == null || newDef == null) { |
| throw new IllegalArgumentException("arguments can not be null"); |
| } |
| if (!oldDef.getName().equals(newDef.getName())) { |
| throw new IllegalArgumentException("at least node type names must be matching"); |
| } |
| return new NodeTypeDefDiff(oldDef, newDef); |
| } |
| |
| /** |
| * @return <code>true</code> if modified |
| */ |
| public boolean isModified() { |
| return type != NONE; |
| } |
| |
| /** |
| * @return <code>true</code> if trivial |
| */ |
| public boolean isTrivial() { |
| return type == TRIVIAL; |
| } |
| |
| /** |
| * @return <code>true</code> if major |
| */ |
| public boolean isMajor() { |
| return type == MAJOR; |
| } |
| |
| /** |
| * Returns the type of modification as expressed by the following constants: |
| * <ul> |
| * <li><b><code>NONE</code></b>: no modification at all |
| * <li><b><code>TRIVIAL</code></b>: does not affect consistency of |
| * existing content |
| * <li><b><code>MAJOR</code></b>: <i>does</i> affect consistency of existing |
| * content |
| * </ul> |
| * |
| * @return the type of modification |
| */ |
| public int getType() { |
| return type; |
| } |
| |
| /** |
| * @return <code>true</code> if mixin flag diff |
| */ |
| public int mixinFlagDiff() { |
| return oldDef.isMixin() != newDef.isMixin() ? MAJOR : NONE; |
| } |
| |
| /** |
| * @return <code>true</code> if abstract flag diff |
| */ |
| public int abstractFlagDiff() { |
| return oldDef.isAbstract() && !newDef.isAbstract() ? MAJOR : NONE; |
| } |
| |
| /** |
| * @return <code>true</code> if supertypes diff |
| */ |
| public int supertypesDiff() { |
| Set<String> set1 = getDeclaredSuperTypeNames(oldDef); |
| Set<String> set2 = getDeclaredSuperTypeNames(newDef); |
| return !set1.equals(set2) ? MAJOR : NONE; |
| } |
| |
| /** |
| * Returns the set of declared supertype names without 'nt:base', which is |
| * irrelevant for a diff of supertypes. |
| * |
| * @param def a NodeTypeDefinition. |
| * @return the set of declared supertype names. |
| */ |
| private Set<String> getDeclaredSuperTypeNames(NodeTypeDefinition def) { |
| Set<String> names = new HashSet<String>(Arrays.asList(def.getDeclaredSupertypeNames())); |
| names.remove(JcrConstants.NT_BASE); |
| return names; |
| } |
| |
| /** |
| * @return diff type |
| */ |
| private int buildPropDefDiffs() { |
| int maxType = NONE; |
| Map<PropertyDefinitionId, PropertyDefinition> oldDefs = new HashMap<PropertyDefinitionId, PropertyDefinition>(); |
| for (PropertyDefinition def : oldDef.getDeclaredPropertyDefinitions()) { |
| oldDefs.put(new PropertyDefinitionId(def), def); |
| } |
| |
| Map<PropertyDefinitionId, PropertyDefinition> newDefs = new HashMap<PropertyDefinitionId, PropertyDefinition>(); |
| for (PropertyDefinition def : newDef.getDeclaredPropertyDefinitions()) { |
| newDefs.put(new PropertyDefinitionId(def), def); |
| } |
| |
| /** |
| * walk through defs1 and process all entries found in |
| * both defs1 & defs2 and those found only in defs1 |
| */ |
| for (Map.Entry<PropertyDefinitionId, PropertyDefinition> entry : oldDefs.entrySet()) { |
| PropertyDefinitionId id = entry.getKey(); |
| PropertyDefinition def1 = entry.getValue(); |
| PropertyDefinition def2 = newDefs.get(id); |
| PropDefDiff diff = new PropDefDiff(def1, def2); |
| if (diff.getType() > maxType) { |
| maxType = diff.getType(); |
| } |
| propDefDiffs.add(diff); |
| newDefs.remove(id); |
| } |
| |
| /** |
| * defs2 by now only contains entries found in defs2 only; |
| * walk through defs2 and process all remaining entries |
| */ |
| for (Map.Entry<PropertyDefinitionId, PropertyDefinition> entry : newDefs.entrySet()) { |
| PropertyDefinition def = entry.getValue(); |
| PropDefDiff diff = new PropDefDiff(null, def); |
| if (diff.getType() > maxType) { |
| maxType = diff.getType(); |
| } |
| propDefDiffs.add(diff); |
| } |
| |
| return maxType; |
| } |
| |
| /** |
| * @return diff type |
| */ |
| private int buildChildNodeDefDiffs() { |
| int maxType = NONE; |
| final Map<NodeDefinitionId, List<NodeDefinition>> oldDefs = collectChildNodeDefs(oldDef.getDeclaredChildNodeDefinitions()); |
| final Map<NodeDefinitionId, List<NodeDefinition>> newDefs = collectChildNodeDefs(newDef.getDeclaredChildNodeDefinitions()); |
| |
| for (NodeDefinitionId defId : oldDefs.keySet()) { |
| final ChildNodeDefDiffs childNodeDefDiffs = new ChildNodeDefDiffs(oldDefs.get(defId), newDefs.get(defId)); |
| this.childNodeDefDiffs.addAll(childNodeDefDiffs.getChildNodeDefDiffs()); |
| newDefs.remove(defId); |
| } |
| |
| for (NodeDefinitionId defId : newDefs.keySet()) { |
| final ChildNodeDefDiffs childNodeDefDiffs = new ChildNodeDefDiffs(null, newDefs.get(defId)); |
| this.childNodeDefDiffs.addAll(childNodeDefDiffs.getChildNodeDefDiffs()); |
| } |
| |
| for (ChildNodeDefDiff diff : childNodeDefDiffs) { |
| if (diff.getType() > maxType) { |
| maxType = diff.getType(); |
| } |
| } |
| |
| return maxType; |
| } |
| |
| private Map<NodeDefinitionId, List<NodeDefinition>> collectChildNodeDefs(final NodeDefinition[] cnda1) { |
| Map<NodeDefinitionId, List<NodeDefinition>> defs1 = new HashMap<NodeDefinitionId, List<NodeDefinition>>(); |
| for (NodeDefinition def1 : cnda1) { |
| final NodeDefinitionId def1Id = new NodeDefinitionId(def1); |
| List<NodeDefinition> list = defs1.get(def1Id); |
| if (list == null) { |
| list = new ArrayList<NodeDefinition>(); |
| defs1.put(def1Id, list); |
| } |
| list.add(def1); |
| } |
| return defs1; |
| } |
| |
| @Override |
| public String toString() { |
| String result = getClass().getName() + "[\n\tnodeTypeName=" |
| + oldDef.getName(); |
| |
| result += ",\n\tmixinFlagDiff=" + modificationTypeToString(mixinFlagDiff()); |
| result += ",\n\tsupertypesDiff=" + modificationTypeToString(supertypesDiff()); |
| |
| result += ",\n\tpropertyDifferences=[\n"; |
| result += toString(propDefDiffs); |
| result += "\t]"; |
| |
| result += ",\n\tchildNodeDifferences=[\n"; |
| result += toString(childNodeDefDiffs); |
| result += "\t]\n"; |
| result += "]\n"; |
| |
| return result; |
| } |
| |
| private String toString(List<? extends ChildItemDefDiff> childItemDefDiffs) { |
| String result = ""; |
| for (Iterator<? extends ChildItemDefDiff> iter = childItemDefDiffs.iterator(); iter.hasNext();) { |
| ChildItemDefDiff propDefDiff = iter.next(); |
| result += "\t\t" + propDefDiff; |
| if (iter.hasNext()) { |
| result += ","; |
| } |
| result += "\n"; |
| } |
| return result; |
| } |
| |
| private String modificationTypeToString(int modificationType) { |
| String typeString = "unknown"; |
| switch (modificationType) { |
| case NONE: |
| typeString = "NONE"; |
| break; |
| case TRIVIAL: |
| typeString = "TRIVIAL"; |
| break; |
| case MAJOR: |
| typeString = "MAJOR"; |
| break; |
| } |
| return typeString; |
| } |
| |
| |
| //--------------------------------------------------------< inner classes > |
| |
| abstract class ChildItemDefDiff { |
| protected final ItemDefinition oldDef; |
| protected final ItemDefinition newDef; |
| protected int type; |
| |
| ChildItemDefDiff(ItemDefinition oldDef, ItemDefinition newDef) { |
| this.oldDef = oldDef; |
| this.newDef = newDef; |
| init(); |
| } |
| |
| protected void init() { |
| // determine type of modification |
| if (isAdded()) { |
| if (!newDef.isMandatory()) { |
| // adding a non-mandatory child item is a TRIVIAL change |
| type = TRIVIAL; |
| } else { |
| // adding a mandatory child item is a MAJOR change |
| type = MAJOR; |
| } |
| } else if (isRemoved()) { |
| // removing a child item is a MAJOR change |
| type = MAJOR; |
| } else { |
| /** |
| * neither added nor removed => has to be either identical |
| * or modified |
| */ |
| if (oldDef.equals(newDef)) { |
| // identical |
| type = NONE; |
| } else { |
| // modified |
| if (oldDef.isMandatory() != newDef.isMandatory() |
| && newDef.isMandatory()) { |
| // making a child item mandatory is a MAJOR change |
| type = MAJOR; |
| } else { |
| if (!"*".equals(oldDef.getName()) |
| && "*".equals(newDef.getName())) { |
| // just making a child item residual is a TRIVIAL change |
| type = TRIVIAL; |
| } else { |
| if (!oldDef.getName().equals(newDef.getName())) { |
| // changing the name of a child item is a MAJOR change |
| type = MAJOR; |
| } else { |
| // all other changes are TRIVIAL |
| type = TRIVIAL; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| public int getType() { |
| return type; |
| } |
| |
| public boolean isAdded() { |
| return oldDef == null && newDef != null; |
| } |
| |
| public boolean isRemoved() { |
| return oldDef != null && newDef == null; |
| } |
| |
| public boolean isModified() { |
| return oldDef != null && newDef != null |
| && !oldDef.equals(newDef); |
| } |
| |
| @Override |
| public String toString() { |
| String typeString = modificationTypeToString(getType()); |
| |
| String operationString; |
| if (isAdded()) { |
| operationString = "ADDED"; |
| } else if (isModified()) { |
| operationString = "MODIFIED"; |
| } else if (isRemoved()) { |
| operationString = "REMOVED"; |
| } else { |
| operationString = "NONE"; |
| } |
| |
| ItemDefinition itemDefinition = (oldDef != null) ? oldDef : newDef; |
| |
| return getClass().getName() + "[itemName=" |
| + itemDefinition.getName() + ", type=" + typeString |
| + ", operation=" + operationString + "]"; |
| } |
| |
| } |
| |
| public class PropDefDiff extends ChildItemDefDiff { |
| |
| PropDefDiff(PropertyDefinition oldDef, PropertyDefinition newDef) { |
| super(oldDef, newDef); |
| } |
| |
| public PropertyDefinition getOldDef() { |
| return (PropertyDefinition) oldDef; |
| } |
| |
| public PropertyDefinition getNewDef() { |
| return (PropertyDefinition) newDef; |
| } |
| |
| @Override |
| protected void init() { |
| super.init(); |
| /** |
| * only need to do comparison if base class implementation |
| * detected a non-MAJOR (i.e. TRIVIAL) modification; |
| * no need to check for additions or removals as this is already |
| * handled in base class implementation. |
| */ |
| if (isModified() && type == TRIVIAL) { |
| // check if valueConstraints were made more restrictive |
| String[] vca1 = getOldDef().getValueConstraints(); |
| Set<String> set1 = new HashSet<String>(); |
| for (String aVca1 : vca1) { |
| set1.add(aVca1); |
| } |
| String[] vca2 = getNewDef().getValueConstraints(); |
| Set<String> set2 = new HashSet<String>(); |
| for (String aVca2 : vca2) { |
| set2.add(aVca2); |
| } |
| |
| if (!set1.equals(set2)) { |
| // valueConstraints have been modified |
| if (set2.isEmpty()) { |
| // all existing constraints have been cleared |
| // => TRIVIAL change |
| type = TRIVIAL; |
| } else if (set1.isEmpty()) { |
| // constraints have been set on a previously unconstrained property |
| // => MAJOR change |
| type = MAJOR; |
| } else if (set2.containsAll(set1)) { |
| // new set is a superset of old set, |
| // i.e. constraints have been weakened |
| // (since constraints are OR'ed) |
| // => TRIVIAL change |
| type = TRIVIAL; |
| } else { |
| // constraint have been removed/modified (MAJOR change); |
| // since we're unable to semantically compare |
| // value constraints (e.g. regular expressions), all |
| // such modifications are considered a MAJOR change. |
| type = MAJOR; |
| } |
| } |
| |
| // no need to check defaultValues (TRIVIAL change) |
| // no need to check availableQueryOperators (TRIVIAL change) |
| // no need to check queryOrderable (TRIVIAL change) |
| |
| if (type == TRIVIAL) { |
| int t1 = getOldDef().getRequiredType(); |
| int t2 = getNewDef().getRequiredType(); |
| if (t1 != t2) { |
| if (t2 == PropertyType.UNDEFINED) { |
| // changed getRequiredType to UNDEFINED (TRIVIAL change) |
| type = TRIVIAL; |
| } else { |
| // changed getRequiredType to specific type (MAJOR change) |
| type = MAJOR; |
| } |
| } |
| boolean b1 = getOldDef().isMultiple(); |
| boolean b2 = getNewDef().isMultiple(); |
| if (b1 != b2) { |
| if (b2) { |
| // changed multiple flag to true (TRIVIAL change) |
| type = TRIVIAL; |
| } else { |
| // changed multiple flag to false (MAJOR change) |
| type = MAJOR; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| public class ChildNodeDefDiff extends ChildItemDefDiff { |
| |
| ChildNodeDefDiff(NodeDefinition oldDef, NodeDefinition newDef) { |
| super(oldDef, newDef); |
| } |
| |
| public NodeDefinition getOldDef() { |
| return (NodeDefinition) oldDef; |
| } |
| |
| public NodeDefinition getNewDef() { |
| return (NodeDefinition) newDef; |
| } |
| |
| @Override |
| protected void init() { |
| super.init(); |
| /** |
| * only need to do comparison if base class implementation |
| * detected a non-MAJOR (i.e. TRIVIAL) modification; |
| * no need to check for additions or removals as this is already |
| * handled in base class implementation. |
| */ |
| if (isModified() && type == TRIVIAL) { |
| |
| boolean b1 = getOldDef().allowsSameNameSiblings(); |
| boolean b2 = getNewDef().allowsSameNameSiblings(); |
| if (b1 != b2 && !b2) { |
| // changed sameNameSiblings flag to false (MAJOR change) |
| type = MAJOR; |
| } |
| |
| // no need to check defaultPrimaryType (TRIVIAL change) |
| |
| if (type == TRIVIAL) { |
| Set<String> s1 = new HashSet<String>(Arrays.asList(getOldDef().getRequiredPrimaryTypeNames())); |
| Set<String> s2 = new HashSet<String>(Arrays.asList(getNewDef().getRequiredPrimaryTypeNames())); |
| // normalize sets by removing nt:base (adding/removing nt:base is irrelevant for the diff) |
| s1.remove("nt:base"); |
| s2.remove("nt:base"); |
| if (!s1.equals(s2)) { |
| // requiredPrimaryTypes have been modified |
| if (s1.containsAll(s2)) { |
| // old list is a superset of new list |
| // => removed requiredPrimaryType (TRIVIAL change) |
| type = TRIVIAL; |
| } else { |
| // added/modified requiredPrimaryType (MAJOR change) |
| // todo check whether aggregate of old requiredTypes would include aggregate of new requiredTypes => trivial change |
| type = MAJOR; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Identifier used to identify corresponding property definitions |
| */ |
| static class PropertyDefinitionId { |
| |
| String declaringNodeType; |
| String name; |
| boolean definesResidual; |
| |
| PropertyDefinitionId(PropertyDefinition def) { |
| declaringNodeType = def.getDeclaringNodeType().getName(); |
| name = def.getName(); |
| definesResidual = "*".equals(def.getName()); |
| } |
| |
| //---------------------------------------< java.lang.Object overrides > |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj instanceof PropertyDefinitionId) { |
| PropertyDefinitionId other = (PropertyDefinitionId) obj; |
| return declaringNodeType.equals(other.declaringNodeType) |
| && name.equals(other.name) |
| && definesResidual == other.definesResidual; |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| int h = 17; |
| h = 37 * h + declaringNodeType.hashCode(); |
| h = 37 * h + name.hashCode(); |
| h = 37 * h + (definesResidual ? 11 : 43); |
| return h; |
| } |
| } |
| |
| /** |
| * Identifier used to identify corresponding node definitions |
| */ |
| static class NodeDefinitionId { |
| |
| String declaringNodeType; |
| String name; |
| |
| NodeDefinitionId(NodeDefinition def) { |
| declaringNodeType = def.getDeclaringNodeType().getName(); |
| name = def.getName(); |
| } |
| |
| //---------------------------------------< java.lang.Object overrides > |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj instanceof NodeDefinitionId) { |
| NodeDefinitionId other = (NodeDefinitionId) obj; |
| return declaringNodeType.equals(other.declaringNodeType) |
| && name.equals(other.name); |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| int h = 17; |
| h = 37 * h + declaringNodeType.hashCode(); |
| h = 37 * h + name.hashCode(); |
| return h; |
| } |
| } |
| |
| private class ChildNodeDefDiffs { |
| |
| private final List<NodeDefinition> defs1; |
| private final List<NodeDefinition> defs2; |
| |
| private ChildNodeDefDiffs(final List<NodeDefinition> defs1, final List<NodeDefinition> defs2) { |
| this.defs1 = defs1 != null ? defs1 : Collections.<NodeDefinition>emptyList(); |
| this.defs2 = defs2 != null ? defs2 : Collections.<NodeDefinition>emptyList(); |
| } |
| |
| private Collection<ChildNodeDefDiff> getChildNodeDefDiffs() { |
| // gather all possible combinations of diffs |
| final List<ChildNodeDefDiff> diffs = new ArrayList<ChildNodeDefDiff>(); |
| for (NodeDefinition def1 : defs1) { |
| for (NodeDefinition def2 : defs2) { |
| diffs.add(new ChildNodeDefDiff(def1, def2)); |
| } |
| } |
| if (defs2.size() < defs1.size()) { |
| for (NodeDefinition def1 : defs1) { |
| diffs.add(new ChildNodeDefDiff(def1, null)); |
| } |
| } |
| if (defs1.size() < defs2.size()) { |
| for (NodeDefinition def2 : defs2) { |
| diffs.add(new ChildNodeDefDiff(null, def2)); |
| } |
| } |
| // sort them according to decreasing compatibility |
| Collections.sort(diffs, new Comparator<ChildNodeDefDiff>() { |
| @Override |
| public int compare(final ChildNodeDefDiff o1, final ChildNodeDefDiff o2) { |
| return o1.getType() - o2.getType(); |
| } |
| }); |
| // select the most compatible ones |
| final int size = defs1.size() > defs2.size() ? defs1.size() : defs2.size(); |
| AtomicInteger allowedNewNull = new AtomicInteger(defs1.size() - defs2.size()); |
| AtomicInteger allowedOldNull = new AtomicInteger(defs2.size() - defs1.size()); |
| final List<ChildNodeDefDiff> results = new ArrayList<ChildNodeDefDiff>(); |
| for (ChildNodeDefDiff diff : diffs) { |
| if (!alreadyMatched(results, diff.getNewDef(), diff.getOldDef(), allowedNewNull, allowedOldNull)) { |
| results.add(diff); |
| if (diff.getNewDef() == null) { |
| allowedNewNull.decrementAndGet(); |
| } |
| if (diff.getOldDef() == null) { |
| allowedOldNull.decrementAndGet(); |
| } |
| } |
| if (results.size() == size) { |
| break; |
| } |
| } |
| return results; |
| } |
| |
| private boolean alreadyMatched(final List<ChildNodeDefDiff> result, final NodeDefinition newDef, final NodeDefinition oldDef, final AtomicInteger allowedNewNull, final AtomicInteger allowedOldNull) { |
| boolean containsNewDef = false, containsOldDef = false; |
| for (ChildNodeDefDiff d : result) { |
| if (d.getNewDef() != null && d.getNewDef().equals(newDef)) { |
| containsNewDef = true; |
| break; |
| } |
| if (d.getOldDef() != null && d.getOldDef().equals(oldDef)) { |
| containsOldDef = true; |
| break; |
| } |
| } |
| if (oldDef == null) { |
| if (allowedOldNull.get() < 1) { |
| containsOldDef = true; |
| } |
| } |
| if (newDef == null) { |
| if (allowedNewNull.get() < 1) { |
| containsNewDef = true; |
| } |
| } |
| |
| return containsNewDef || containsOldDef; |
| } |
| } |
| |
| } |