blob: 9d48970a7a6104d45b55d540ba4e8feef839a450 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2017 IBM Corporation and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
*******************************************************************************/
package org.eclipse.dltk.internal.core;
import java.util.ArrayList;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.dltk.core.IModelElement;
import org.eclipse.dltk.core.IModelElementDelta;
public class ModelElementDelta extends SimpleDelta
implements IModelElementDelta {
/**
* Empty array of IModelElementDelta
*/
protected static final IModelElementDelta[] EMPTY_DELTA = new IModelElementDelta[] {};
protected IModelElementDelta[] affectedChildren = EMPTY_DELTA;
/*
* The element that this delta describes the change to.
*/
protected IModelElement changedElement;
/**
* Collection of resource deltas that correspond to nonscriptresources
* deltas.
*/
protected IResourceDelta[] resourceDeltas = null;
/**
* Counter of resource deltas
*/
protected int resourceDeltasCounter;
/**
* @see #getMovedFromElement()
*/
protected IModelElement movedFromHandle = null;
/**
* @see #getMovedToElement()
*/
protected IModelElement movedToHandle = null;
/**
* Creates the root delta. To create the nested delta hierarchies use the
* following convenience methods. The root delta can be created at any level
* (for example: project, package root, package fragment...).
* <ul>
* <li><code>added(IModelElement)</code>
* <li><code>changed(IModelElement)</code>
* <li><code>moved(IModelElement, IModelElement)</code>
* <li><code>removed(IModelElement)</code>
* <li><code>renamed(IModelElement, IModelElement)</code>
* </ul>
*/
public ModelElementDelta(IModelElement element) {
this.changedElement = element;
}
/**
* Creates the nested deltas resulting from an add operation. Convenience
* method for creating add deltas. The constructor should be used to create
* the root delta and then an add operation should call this method.
*/
public void added(IModelElement element) {
added(element, 0);
}
public void added(IModelElement element, int flags) {
ModelElementDelta addedDelta = new ModelElementDelta(element);
addedDelta.added();
addedDelta.changeFlags |= flags;
insertDeltaTree(element, addedDelta);
}
/**
* Creates the nested deltas resulting from an delete operation. Convenience
* method for creating removed deltas. The constructor should be used to
* create the root delta and then the delete operation should call this
* method.
*/
public void removed(IModelElement element) {
removed(element, 0);
}
public void removed(IModelElement element, int flags) {
ModelElementDelta removedDelta = new ModelElementDelta(element);
insertDeltaTree(element, removedDelta);
ModelElementDelta actualDelta = getDeltaFor(element);
if (actualDelta != null) {
actualDelta.removed();
actualDelta.changeFlags |= flags;
actualDelta.affectedChildren = EMPTY_DELTA;
}
}
/**
* Returns the delta for a given element. Only looks below this delta.
*/
protected ModelElementDelta getDeltaFor(IModelElement element) {
if (this.equalsAndSameParent(getElement(), element)) // handle case of
// two archives
// that can be
// equals but
// not in the
// same project
return this;
if (this.affectedChildren.length == 0)
return null;
int childrenCount = this.affectedChildren.length;
for (int i = 0; i < childrenCount; i++) {
ModelElementDelta delta = (ModelElementDelta) this.affectedChildren[i];
if (this.equalsAndSameParent(delta.getElement(), element)) { // handle
// case
// of
// two
// archives
// that
// can
// be
// equals
// but
// not
// in
// the
// same
// project
return delta;
} else {
delta = delta.getDeltaFor(element);
if (delta != null)
return delta;
}
}
return null;
}
/**
* 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(ModelElementDelta 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 a child delta is added to a compilation unit delta or below,
// it's a fine grained delta
if (this.changedElement
.getElementType() >= IModelElement.SOURCE_MODULE) {
this.fineGrained();
}
if (this.affectedChildren == null
|| this.affectedChildren.length == 0) {
this.affectedChildren = new IModelElementDelta[] { child };
return;
}
ModelElementDelta existingChild = null;
int existingChildIndex = -1;
if (this.affectedChildren != null) {
for (int i = 0; i < this.affectedChildren.length; i++) {
if (this.equalsAndSameParent(
this.affectedChildren[i].getElement(),
child.getElement())) { // handle case of two archives
// that can be equals but not in
// the same project
existingChild = (ModelElementDelta) 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 = this.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
IModelElementDelta[] children = child.getAffectedChildren();
for (int i = 0; i < children.length; i++) {
ModelElementDelta childsChild = (ModelElementDelta) 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;
}
// add the non-java resource deltas if needed
// note that the child delta always takes precedence over
// this existing child delta
// as non-java resource deltas are always created last (by
// the DeltaProcessor)
IResourceDelta[] resDeltas = child.getResourceDeltas();
if (resDeltas != null) {
existingChild.resourceDeltas = resDeltas;
existingChild.resourceDeltasCounter = child.resourceDeltasCounter;
}
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;
}
}
}
@Override
public IModelElement getElement() {
return this.changedElement;
}
/**
* Creates the nested deltas resulting from a change operation. Convenience
* method for creating change deltas. The constructor should be used to
* create the root delta and then a change operation should call this
* method.
*/
public ModelElementDelta changed(IModelElement element, int changeFlag) {
ModelElementDelta changedDelta = new ModelElementDelta(element);
changedDelta.changed(changeFlag);
insertDeltaTree(element, changedDelta);
return changedDelta;
}
/**
* Creates the delta tree for the given element and delta, and then inserts
* the tree as an affected child of this node.
*/
protected void insertDeltaTree(IModelElement element,
ModelElementDelta delta) {
ModelElementDelta childDelta = createDeltaTree(element, delta);
if (!this.equalsAndSameParent(element, getElement())) { // handle case
// of two
// archives 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 ModelElementDelta createDeltaTree(IModelElement element,
ModelElementDelta delta) {
ModelElementDelta childDelta = delta;
ArrayList<IModelElement> ancestors = getAncestors(element);
if (ancestors == null) {
if (this.equalsAndSameParent(delta.getElement(), getElement())) { // handle
// case
// of
// two
// archives
// that
// can
// be
// equals
// but
// not
// in
// the
// same
// project
// the element being changed is the root element
this.kind = delta.kind;
this.changeFlags = delta.changeFlags;
this.movedToHandle = delta.movedToHandle;
this.movedFromHandle = delta.movedFromHandle;
}
} else {
for (int i = 0, size = ancestors.size(); i < size; i++) {
IModelElement ancestor = ancestors.get(i);
ModelElementDelta ancestorDelta = new ModelElementDelta(
ancestor);
ancestorDelta.addAffectedChild(childDelta);
childDelta = ancestorDelta;
}
}
return childDelta;
}
/**
* Returns whether the two script elements are equals and have the same
* parent.
*/
protected boolean equalsAndSameParent(IModelElement e1, IModelElement e2) {
IModelElement parent1;
return e1.equals(e2) && ((parent1 = e1.getParent()) != null)
&& parent1.equals(e2.getParent());
}
@Override
public IModelElementDelta[] getAddedChildren() {
return getChildrenOfType(ADDED);
}
@Override
public IModelElementDelta[] getAffectedChildren() {
return this.affectedChildren;
}
/**
* Returns a collection of all the parents of this element up to (but not
* including) the root of this tree in bottom-up order. If the given element
* is not a descendant of the root of this tree, <code>null</code> is
* returned.
*/
private ArrayList<IModelElement> getAncestors(IModelElement element) {
IModelElement parent = element.getParent();
if (parent == null) {
return null;
}
ArrayList<IModelElement> parents = new ArrayList<>();
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 IModelElementDelta[] growAndAddToArray(IModelElementDelta[] array,
IModelElementDelta addition) {
IModelElementDelta[] old = array;
array = new IModelElementDelta[old.length + 1];
System.arraycopy(old, 0, array, 0, old.length);
array[old.length] = addition;
return array;
}
/**
* Removes the element from the array. Returns the a new array which has
* shrunk.
*/
protected IModelElementDelta[] removeAndShrinkArray(
IModelElementDelta[] old, int index) {
IModelElementDelta[] array = new IModelElementDelta[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;
}
/**
* Mark this delta as a fine-grained delta.
*/
public void fineGrained() {
changed(F_FINE_GRAINED);
}
/**
* Return the collection of resource deltas. Return null if none.
*/
@Override
public IResourceDelta[] getResourceDeltas() {
if (resourceDeltas == null)
return null;
if (resourceDeltas.length != resourceDeltasCounter) {
System.arraycopy(resourceDeltas, 0,
resourceDeltas = new IResourceDelta[resourceDeltasCounter],
0, resourceDeltasCounter);
}
return resourceDeltas;
}
/**
* Removes the child delta from the collection of affected children.
*/
protected void removeAffectedChild(ModelElementDelta child) {
int index = -1;
if (this.affectedChildren != null) {
for (int i = 0; i < this.affectedChildren.length; i++) {
if (this.equalsAndSameParent(
this.affectedChildren[i].getElement(),
child.getElement())) { // handle case of two archives
// that can be equals but not in
// the same project
index = i;
break;
}
}
}
if (index >= 0) {
this.affectedChildren = removeAndShrinkArray(this.affectedChildren,
index);
}
}
/**
* Mark this delta as a content changed delta.
*/
public void contentChanged() {
this.changeFlags |= F_CONTENT;
}
/**
* Creates the nested deltas resulting from an move operation. Convenience
* method for creating the "move from" delta. The constructor should be used
* to create the root delta and then the move operation should call this
* method.
*/
public void movedFrom(IModelElement movedFromElement,
IModelElement movedToElement) {
ModelElementDelta removedDelta = new ModelElementDelta(
movedFromElement);
removedDelta.kind = REMOVED;
removedDelta.changeFlags |= F_MOVED_TO;
removedDelta.movedToHandle = movedToElement;
insertDeltaTree(movedFromElement, removedDelta);
}
/**
* Creates the nested deltas resulting from an move operation. Convenience
* method for creating the "move to" delta. The constructor should be used
* to create the root delta and then the move operation should call this
* method.
*/
public void movedTo(IModelElement movedToElement,
IModelElement movedFromElement) {
ModelElementDelta addedDelta = new ModelElementDelta(movedToElement);
addedDelta.kind = ADDED;
addedDelta.changeFlags |= F_MOVED_FROM;
addedDelta.movedFromHandle = movedFromElement;
insertDeltaTree(movedToElement, addedDelta);
}
protected void addResourceDelta(IResource resource) {
addResourceDelta(new ResourceChangeToNonScriptDelta(resource));
}
/**
* Adds the child delta to the collection of affected children. If the child
* is already in the collection, walk down the hierarchy.
*/
protected void addResourceDelta(IResourceDelta 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_CONTENT;
break;
default:
this.kind = CHANGED;
this.changeFlags |= F_CONTENT;
}
if (resourceDeltas == null) {
resourceDeltas = new IResourceDelta[5];
resourceDeltas[resourceDeltasCounter++] = child;
return;
}
if (resourceDeltas.length == resourceDeltasCounter) {
// need a resize
System.arraycopy(resourceDeltas, 0,
(resourceDeltas = new IResourceDelta[resourceDeltasCounter
* 2]),
0, resourceDeltasCounter);
}
resourceDeltas[resourceDeltasCounter++] = child;
}
/**
* Returns the <code>ModelElementDelta</code> for the given element in the
* delta tree, or null, if no delta for the given element is found.
*/
protected ModelElementDelta find(IModelElement e) {
if (this.equalsAndSameParent(this.changedElement, e)) { // handle case
// of two
// archives that
// can be equals
// but not in
// the same
// project
return this;
} else {
for (int i = 0; i < this.affectedChildren.length; i++) {
ModelElementDelta delta = ((ModelElementDelta) this.affectedChildren[i])
.find(e);
if (delta != null) {
return delta;
}
}
}
return null;
}
/**
* Creates the nested deltas for a closed element.
*/
public void closed(IModelElement element) {
ModelElementDelta delta = new ModelElementDelta(element);
delta.changed(F_CLOSED);
insertDeltaTree(element, delta);
}
/**
* Creates the nested deltas for an opened element.
*/
public void opened(IModelElement element) {
ModelElementDelta delta = new ModelElementDelta(element);
delta.changed(F_OPENED);
insertDeltaTree(element, delta);
}
/**
* @see IModelElementDelta
*/
protected IModelElementDelta[] getChildrenOfType(int type) {
int length = affectedChildren.length;
if (length == 0) {
return new IModelElementDelta[] {};
}
ArrayList<IModelElementDelta> children = new ArrayList<>(
length);
for (int i = 0; i < length; i++) {
if (affectedChildren[i].getKind() == type) {
children.add(affectedChildren[i]);
}
}
IModelElementDelta[] childrenOfType = new IModelElementDelta[children
.size()];
children.toArray(childrenOfType);
return childrenOfType;
}
@Override
public IModelElement getMovedFromElement() {
return this.movedFromHandle;
}
@Override
public IModelElement getMovedToElement() {
return movedToHandle;
}
/**
* Returns a string representation of this delta's structure suitable for
* debug purposes.
*
* @see #toString()
*/
public String toDebugString(int depth) {
StringBuffer buffer = new StringBuffer();
for (int i = 0; i < depth; i++) {
buffer.append('\t');
}
buffer.append(((ModelElement) getElement()).toDebugString());
toDebugString(buffer);
IModelElementDelta[] children = getAffectedChildren();
if (children != null) {
for (int i = 0; i < children.length; ++i) {
buffer.append("\n"); //$NON-NLS-1$
buffer.append(((ModelElementDelta) children[i])
.toDebugString(depth + 1));
}
}
for (int i = 0; i < resourceDeltasCounter; i++) {
buffer.append("\n");//$NON-NLS-1$
for (int j = 0; j < depth + 1; j++) {
buffer.append('\t');
}
IResourceDelta resourceDelta = resourceDeltas[i];
buffer.append(resourceDelta.toString());
buffer.append("["); //$NON-NLS-1$
switch (resourceDelta.getKind()) {
case IResourceDelta.ADDED:
buffer.append('+');
break;
case IResourceDelta.REMOVED:
buffer.append('-');
break;
case IResourceDelta.CHANGED:
buffer.append('*');
break;
default:
buffer.append('?');
break;
}
buffer.append("]"); //$NON-NLS-1$
}
return buffer.toString();
}
@Override
protected boolean toDebugString(StringBuffer buffer, int flags) {
boolean prev = super.toDebugString(buffer, flags);
if ((flags & IModelElementDelta.F_CHILDREN) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("CHILDREN"); //$NON-NLS-1$
prev = true;
}
if ((flags & IModelElementDelta.F_CONTENT) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("CONTENT"); //$NON-NLS-1$
prev = true;
}
if ((flags & IModelElementDelta.F_MOVED_FROM) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("MOVED_FROM(" + ((ModelElement) getMovedFromElement()) //$NON-NLS-1$
.toStringWithAncestors() + ")"); //$NON-NLS-1$
prev = true;
}
if ((flags & IModelElementDelta.F_MOVED_TO) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("MOVED_TO(" + ((ModelElement) getMovedToElement()) //$NON-NLS-1$
.toStringWithAncestors() + ")"); //$NON-NLS-1$
prev = true;
}
if ((flags & IModelElementDelta.F_ADDED_TO_BUILDPATH) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("ADDED TO BUILDPATH"); //$NON-NLS-1$
prev = true;
}
if ((flags & IModelElementDelta.F_REMOVED_FROM_BUILDPATH) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("REMOVED FROM BUILDPATH"); //$NON-NLS-1$
prev = true;
}
if ((flags & IModelElementDelta.F_REORDER) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("REORDERED"); //$NON-NLS-1$
prev = true;
}
if ((flags & IModelElementDelta.F_ARCHIVE_CONTENT_CHANGED) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("ARCHIVE CONTENT CHANGED"); //$NON-NLS-1$
prev = true;
}
if ((flags & IModelElementDelta.F_FINE_GRAINED) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("FINE GRAINED"); //$NON-NLS-1$
prev = true;
}
if ((flags & IModelElementDelta.F_PRIMARY_WORKING_COPY) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("PRIMARY WORKING COPY"); //$NON-NLS-1$
prev = true;
}
if ((flags & IModelElementDelta.F_BUILDPATH_CHANGED) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("BUILDPATH CHANGED"); //$NON-NLS-1$
prev = true;
}
if ((flags & IModelElementDelta.F_RESOLVED_BUILDPATH_CHANGED) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("RESOLVED BUILDPATH CHANGED"); //$NON-NLS-1$
prev = true;
}
if ((flags & IModelElementDelta.F_PRIMARY_RESOURCE) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("PRIMARY RESOURCE"); //$NON-NLS-1$
prev = true;
}
if ((flags & IModelElementDelta.F_OPENED) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("OPENED"); //$NON-NLS-1$
prev = true;
}
if ((flags & IModelElementDelta.F_CLOSED) != 0) {
if (prev)
buffer.append(" | "); //$NON-NLS-1$
buffer.append("CLOSED"); //$NON-NLS-1$
prev = true;
}
return prev;
}
/**
* Returns a string representation of this delta's structure suitable for
* debug purposes.
*/
@Override
public String toString() {
return toDebugString(0);
}
}