/******************************************************************************* * Copyright (c) 2014, 2015 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html * *******************************************************************************/ package com.cisco.yangide.core.indexing; import java.util.ArrayList; import com.cisco.yangide.core.IOpenable; import com.cisco.yangide.core.IYangElementDelta; import com.cisco.yangide.core.model.YangElement; /** * @author Konstantin Zaitsev * @date Jun 30, 2014 */ public class YangElementDelta implements IYangElementDelta { static IYangElementDelta[] EMPTY_DELTA = new IYangElementDelta[0]; private YangElement changedElement; private int kind = 0; private int flags = 0; private IYangElementDelta[] affectedChildren = EMPTY_DELTA; private int changeFlags; public YangElementDelta(YangElement element) { this.changedElement = element; } @Override public YangElement getElement() { return changedElement; } @Override public int getKind() { return kind; } @Override public int getFlags() { return flags; } @Override public IYangElementDelta[] getAffectedChildren() { return affectedChildren; } public void added(YangElement element) { YangElementDelta addedDelta = new YangElementDelta(element); addedDelta.kind = IYangElementDelta.ADDED; insertDeltaTree(element, addedDelta); } public YangElementDelta changed(YangElement element, int changeFlag) { YangElementDelta changedDelta = new YangElementDelta(element); changedDelta.kind = IYangElementDelta.CHANGED; changedDelta.changeFlags |= flags; insertDeltaTree(element, changedDelta); return changedDelta; } public void removed(YangElement element) { YangElementDelta removedDelta = new YangElementDelta(element); insertDeltaTree(element, removedDelta); YangElementDelta actualDelta = getDeltaFor(element); if (actualDelta != null) { actualDelta.kind = IYangElementDelta.REMOVED; actualDelta.affectedChildren = EMPTY_DELTA; } } protected void insertDeltaTree(YangElement element, YangElementDelta delta) { YangElementDelta childDelta = createDeltaTree(element, delta); if (!equalsAndSameParent(element, getElement())) { // handle case of two jars that can be // equals but not in the same project addAffectedChild(childDelta); } } /** * Creates the nested delta deltas based on the affected element its delta, and the root of this * delta tree. Returns the root of the created delta tree. */ protected YangElementDelta createDeltaTree(YangElement element, YangElementDelta delta) { YangElementDelta childDelta = delta; ArrayList<IOpenable> ancestors = getAncestors(element); if (ancestors == null) { if (equalsAndSameParent(delta.getElement(), getElement())) { this.kind = delta.kind; } } else { for (int i = 0, size = ancestors.size(); i < size; i++) { YangElement ancestor = (YangElement) ancestors.get(i); YangElementDelta ancestorDelta = new YangElementDelta(ancestor); ancestorDelta.addAffectedChild(childDelta); childDelta = ancestorDelta; } } return childDelta; } /** * Adds the child delta to the collection of affected children. If the child is already in the * collection, walk down the hierarchy. */ protected void addAffectedChild(YangElementDelta child) { switch (this.kind) { case ADDED: case REMOVED: // no need to add a child if this parent is added or removed return; case CHANGED: this.changeFlags |= F_CHILDREN; break; default: this.kind = CHANGED; this.changeFlags |= F_CHILDREN; } if (this.affectedChildren == null || this.affectedChildren.length == 0) { this.affectedChildren = new IYangElementDelta[] { child }; return; } YangElementDelta existingChild = null; int existingChildIndex = -1; for (int i = 0; i < this.affectedChildren.length; i++) { if (equalsAndSameParent(this.affectedChildren[i].getElement(), child.getElement())) { existingChild = (YangElementDelta) this.affectedChildren[i]; existingChildIndex = i; break; } } if (existingChild == null) { // new affected child this.affectedChildren = growAndAddToArray(this.affectedChildren, child); } else { switch (existingChild.getKind()) { case ADDED: switch (child.getKind()) { case ADDED: // child was added then added -> it is added case CHANGED: // child was added then changed -> it is added return; case REMOVED: // child was added then removed -> noop this.affectedChildren = removeAndShrinkArray(this.affectedChildren, existingChildIndex); return; } break; case REMOVED: switch (child.getKind()) { case ADDED: // child was removed then added -> it is changed child.kind = CHANGED; this.affectedChildren[existingChildIndex] = child; return; case CHANGED: // child was removed then changed -> it is removed case REMOVED: // child was removed then removed -> it is removed return; } break; case CHANGED: switch (child.getKind()) { case ADDED: // child was changed then added -> it is added case REMOVED: // child was changed then removed -> it is removed this.affectedChildren[existingChildIndex] = child; return; case CHANGED: // child was changed then changed -> it is changed IYangElementDelta[] children = child.getAffectedChildren(); for (int i = 0; i < children.length; i++) { YangElementDelta childsChild = (YangElementDelta) children[i]; existingChild.addAffectedChild(childsChild); } // update flags boolean childHadContentFlag = (child.changeFlags & F_CONTENT) != 0; boolean existingChildHadChildrenFlag = (existingChild.changeFlags & F_CHILDREN) != 0; existingChild.changeFlags |= child.changeFlags; // remove F_CONTENT flag if existing child had F_CHILDREN flag set // (case of fine grained delta (existing child) and delta coming from // DeltaProcessor (child)) if (childHadContentFlag && existingChildHadChildrenFlag) { existingChild.changeFlags &= ~F_CONTENT; } return; } break; default: // unknown -> existing child becomes the child with the existing child's flags int flags = existingChild.getFlags(); this.affectedChildren[existingChildIndex] = child; child.changeFlags |= flags; } } } /** * Returns whether the two yang elements are equals and have the same parent. */ protected boolean equalsAndSameParent(YangElement e1, YangElement e2) { IOpenable parent1; return e1.equals(e2) && ((parent1 = e1.getParent()) != null) && parent1.equals(e2.getParent()); } private ArrayList<IOpenable> getAncestors(YangElement element) { IOpenable parent = element.getParent(); if (parent == null) { return null; } ArrayList<IOpenable> parents = new ArrayList<IOpenable>(); while (!parent.equals(this.changedElement)) { parents.add(parent); parent = parent.getParent(); if (parent == null) { return null; } } parents.trimToSize(); return parents; } /** * Adds the new element to a new array that contains all of the elements of the old array. * Returns the new array. */ protected IYangElementDelta[] growAndAddToArray(IYangElementDelta[] array, IYangElementDelta addition) { IYangElementDelta[] old = array; array = new IYangElementDelta[old.length + 1]; System.arraycopy(old, 0, array, 0, old.length); array[old.length] = addition; return array; } /** * Removes the child delta from the collection of affected children. */ protected void removeAffectedChild(YangElementDelta child) { int index = -1; if (this.affectedChildren != null) { for (int i = 0; i < this.affectedChildren.length; i++) { if (equalsAndSameParent(this.affectedChildren[i].getElement(), child.getElement())) { index = i; break; } } } if (index >= 0) { this.affectedChildren = removeAndShrinkArray(this.affectedChildren, index); } } /** * Returns the delta for a given element. Only looks below this delta. */ protected YangElementDelta getDeltaFor(YangElement element) { if (equalsAndSameParent(getElement(), element)) { return this; } if (this.affectedChildren.length == 0) { return null; } int childrenCount = this.affectedChildren.length; for (int i = 0; i < childrenCount; i++) { YangElementDelta delta = (YangElementDelta) this.affectedChildren[i]; if (equalsAndSameParent(delta.getElement(), element)) { return delta; } else { delta = delta.getDeltaFor(element); if (delta != null) return delta; } } return null; } /** * Removes the element from the array. Returns the a new array which has shrunk. */ protected IYangElementDelta[] removeAndShrinkArray(IYangElementDelta[] old, int index) { IYangElementDelta[] array = new IYangElementDelta[old.length - 1]; if (index > 0) System.arraycopy(old, 0, array, 0, index); int rest = old.length - index - 1; if (rest > 0) System.arraycopy(old, index + 1, array, index, rest); return array; } }