blob: 3d43f5723576e7c002598c25b8bd0fb236c689db [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2018 IBM Corporation and others.
*
* This program and the accompanying materials are made available under
* the terms of the Eclipse Public License 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Vladimir Piskarev (1C) - adaptation (adapted from
* org.eclipse.jdt.internal.core.JavaElementDelta)
*******************************************************************************/
package org.eclipse.handly.model.impl.support;
import static org.eclipse.handly.context.Contexts.EMPTY_CONTEXT;
import static org.eclipse.handly.context.Contexts.of;
import static org.eclipse.handly.context.Contexts.with;
import static org.eclipse.handly.model.IElementDeltaConstants.ADDED;
import static org.eclipse.handly.model.IElementDeltaConstants.CHANGED;
import static org.eclipse.handly.model.IElementDeltaConstants.F_CHILDREN;
import static org.eclipse.handly.model.IElementDeltaConstants.F_CONTENT;
import static org.eclipse.handly.model.IElementDeltaConstants.F_DESCRIPTION;
import static org.eclipse.handly.model.IElementDeltaConstants.F_FINE_GRAINED;
import static org.eclipse.handly.model.IElementDeltaConstants.F_MARKERS;
import static org.eclipse.handly.model.IElementDeltaConstants.F_MOVED_FROM;
import static org.eclipse.handly.model.IElementDeltaConstants.F_MOVED_TO;
import static org.eclipse.handly.model.IElementDeltaConstants.F_OPEN;
import static org.eclipse.handly.model.IElementDeltaConstants.F_REORDER;
import static org.eclipse.handly.model.IElementDeltaConstants.F_SYNC;
import static org.eclipse.handly.model.IElementDeltaConstants.F_UNDERLYING_RESOURCE;
import static org.eclipse.handly.model.IElementDeltaConstants.F_WORKING_COPY;
import static org.eclipse.handly.model.IElementDeltaConstants.NO_CHANGE;
import static org.eclipse.handly.model.IElementDeltaConstants.REMOVED;
import static org.eclipse.handly.util.ToStringOptions.FORMAT_STYLE;
import static org.eclipse.handly.util.ToStringOptions.INDENT_LEVEL;
import static org.eclipse.handly.util.ToStringOptions.INDENT_POLICY;
import static org.eclipse.handly.util.ToStringOptions.FormatStyle.FULL;
import static org.eclipse.handly.util.ToStringOptions.FormatStyle.LONG;
import static org.eclipse.handly.util.ToStringOptions.FormatStyle.MEDIUM;
import static org.eclipse.handly.util.ToStringOptions.FormatStyle.SHORT;
import java.lang.reflect.Array;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IMarkerDelta;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.handly.context.IContext;
import org.eclipse.handly.model.Elements;
import org.eclipse.handly.model.IElement;
import org.eclipse.handly.model.ISourceElement;
import org.eclipse.handly.model.impl.IElementDeltaImpl;
import org.eclipse.handly.util.IndentPolicy;
import org.eclipse.handly.util.ToStringOptions.FormatStyle;
/**
* A complete implementation of {@link IElementDeltaImpl}.
* To create a delta tree, use {@link ElementDelta.Builder}.
* <p>
* Note that, despite having a dependency on {@link IResourceDelta}
* and {@link IMarkerDelta}, this class can be used even when
* <code>org.eclipse.core.resources</code> bundle is not available.
* This is based on the "outward impression" of late resolution of
* symbolic references a JVM must provide according to the JVMS.
* </p>
* <p>
* Clients can use this class as it stands or subclass it as circumstances
* warrant. Clients that subclass this class should consider registering
* an appropriate {@link ElementDelta.Factory} in the model context.
* Subclasses that introduce new fields should consider extending
* the {@link #copyFrom_(ElementDelta, boolean) copyFrom_} method.
* </p>
*/
public class ElementDelta
implements IElementDeltaImpl
{
private static final ElementDelta[] NO_CHILDREN = new ElementDelta[0];
private final IElement element;
private int kind;
private long flags;
private IElement movedFromElement;
private IElement movedToElement;
private ElementDelta[] affectedChildren = NO_CHILDREN;
private int affectedChildrenCounter;
/*
* On-demand index into <code>affectedChildren</code>.
* @see #indexOfChild(Key)
* @see #needsChildIndex_()
*/
private Map<Key, Integer> childIndex;
private IMarkerDelta[] markerDeltas;
private IResourceDelta[] resourceDeltas;
private int resourceDeltasCounter;
/**
* Constructs an initially empty delta for the given element.
*
* @param element not <code>null</code>
* @see ElementDelta.Builder
*/
public ElementDelta(IElement element)
{
if (element == null)
throw new IllegalArgumentException();
this.element = element;
}
@Override
public final IElement getElement_()
{
return element;
}
@Override
public final int getKind_()
{
return kind;
}
@Override
public final long getFlags_()
{
return flags;
}
@Override
public final ElementDelta findDelta_(IElement element)
{
if (element == null)
return null;
if (Elements.equalsAndSameParentChain(this.element, element))
return this;
return findDescendant(new Key(element));
}
/**
* {@inheritDoc}
* <p>
* This implementation returns an array of exactly the same
* runtime type as the array given in the most recent call to
* {@link #setAffectedChildren_(ElementDelta[]) setAffectedChildren_}.
* </p>
*/
@Override
public final ElementDelta[] getAffectedChildren_()
{
if (affectedChildren.length != affectedChildrenCounter)
{
// be careful to preserve the runtime type of affectedChildren
affectedChildren = Arrays.copyOf(affectedChildren,
affectedChildrenCounter);
}
return affectedChildren;
}
/**
* {@inheritDoc}
* <p>
* This implementation returns an array of exactly the same
* runtime type as the array given in the most recent call to
* {@link #setAffectedChildren_(ElementDelta[]) setAffectedChildren_}.
* </p>
*/
@Override
public final ElementDelta[] getAddedChildren_()
{
return getChildrenOfKind(ADDED);
}
/**
* {@inheritDoc}
* <p>
* This implementation returns an array of exactly the same
* runtime type as the array given in the most recent call to
* {@link #setAffectedChildren_(ElementDelta[]) setAffectedChildren_}.
* </p>
*/
@Override
public final ElementDelta[] getRemovedChildren_()
{
return getChildrenOfKind(REMOVED);
}
/**
* {@inheritDoc}
* <p>
* This implementation returns an array of exactly the same
* runtime type as the array given in the most recent call to
* {@link #setAffectedChildren_(ElementDelta[]) setAffectedChildren_}.
* </p>
*/
@Override
public final ElementDelta[] getChangedChildren_()
{
return getChildrenOfKind(CHANGED);
}
@Override
public final IElement getMovedFromElement_()
{
return movedFromElement;
}
@Override
public final IElement getMovedToElement_()
{
return movedToElement;
}
@Override
public final IMarkerDelta[] getMarkerDeltas_()
{
return markerDeltas;
}
@Override
public final IResourceDelta[] getResourceDeltas_()
{
if (resourceDeltas != null
&& resourceDeltas.length != resourceDeltasCounter)
{
System.arraycopy(resourceDeltas, 0, resourceDeltas =
new IResourceDelta[resourceDeltasCounter], 0,
resourceDeltasCounter);
}
return resourceDeltas;
}
@Override
public String toString()
{
return toString_(EMPTY_CONTEXT);
}
@Override
public String toString_(IContext context)
{
StringBuilder builder = new StringBuilder();
IndentPolicy indentPolicy = context.getOrDefault(INDENT_POLICY);
int indentLevel = context.getOrDefault(INDENT_LEVEL);
indentPolicy.appendIndent(builder, indentLevel);
builder.append(Elements.toString(element, with(of(FORMAT_STYLE, SHORT),
of(INDENT_LEVEL, 0), context)));
builder.append('[');
toStringKind_(builder, context);
builder.append("]: {"); //$NON-NLS-1$
toStringFlags_(builder, context);
builder.append('}');
FormatStyle style = context.getOrDefault(FORMAT_STYLE);
if (style == FULL || style == LONG)
{
if (affectedChildrenCounter > 0)
{
indentPolicy.appendLine(builder);
toStringChildren_(builder, with(of(INDENT_LEVEL, //
indentLevel + 1), context));
}
if (resourceDeltasCounter > 0)
{
indentPolicy.appendLine(builder);
toStringResourceDeltas_(builder, with(of(INDENT_LEVEL,
indentLevel + 1), context));
}
}
return builder.toString();
}
protected void toStringChildren_(StringBuilder builder, IContext context)
{
IndentPolicy indentPolicy = context.getOrDefault(INDENT_POLICY);
for (int i = 0; i < affectedChildrenCounter; i++)
{
if (i > 0)
indentPolicy.appendLine(builder);
builder.append(affectedChildren[i].toString_(context));
}
}
protected void toStringResourceDeltas_(StringBuilder builder,
IContext context)
{
IndentPolicy indentPolicy = context.getOrDefault(INDENT_POLICY);
int indentLevel = context.getOrDefault(INDENT_LEVEL);
for (int i = 0; i < resourceDeltasCounter; i++)
{
if (i > 0)
indentPolicy.appendLine(builder);
indentPolicy.appendIndent(builder, indentLevel);
IResourceDelta resourceDelta = resourceDeltas[i];
builder.append("ResourceDelta(" + resourceDelta.getFullPath() //$NON-NLS-1$
+ ')');
builder.append('[');
switch (resourceDelta.getKind())
{
case IResourceDelta.ADDED:
builder.append('+');
break;
case IResourceDelta.REMOVED:
builder.append('-');
break;
case IResourceDelta.CHANGED:
builder.append('*');
break;
default:
builder.append('?');
break;
}
builder.append(']');
}
}
protected void toStringKind_(StringBuilder builder, IContext context)
{
switch (getKind_())
{
case ADDED:
builder.append('+');
break;
case REMOVED:
builder.append('-');
break;
case CHANGED:
builder.append('*');
break;
default:
builder.append('?');
break;
}
}
/**
* Appends a string representation for this delta's flags to the given
* string builder.
*
* @param builder a string builder to append the delta flags to
* @param context not <code>null</code>
* @return <code>true</code> if a flag was appended to the builder,
* and <code>false</code> if the builder was not modified by this method
* @see #getFlags_()
*/
protected boolean toStringFlags_(StringBuilder builder, IContext context)
{
boolean prev = false;
long flags = getFlags_();
if ((flags & F_CHILDREN) != 0)
{
if (prev)
builder.append(" | "); //$NON-NLS-1$
builder.append("CHILDREN"); //$NON-NLS-1$
prev = true;
}
if ((flags & F_CONTENT) != 0)
{
if (prev)
builder.append(" | "); //$NON-NLS-1$
builder.append("CONTENT"); //$NON-NLS-1$
prev = true;
}
if ((flags & F_MOVED_FROM) != 0)
{
if (prev)
builder.append(" | "); //$NON-NLS-1$
builder.append("MOVED_FROM("); //$NON-NLS-1$
builder.append(Elements.toString(getMovedFromElement_(), with(of(
FORMAT_STYLE, MEDIUM), of(INDENT_LEVEL, 0), context)));
builder.append(')');
prev = true;
}
if ((flags & F_MOVED_TO) != 0)
{
if (prev)
builder.append(" | "); //$NON-NLS-1$
builder.append("MOVED_TO("); //$NON-NLS-1$
builder.append(Elements.toString(getMovedToElement_(), with(of(
FORMAT_STYLE, MEDIUM), of(INDENT_LEVEL, 0), context)));
builder.append(')');
prev = true;
}
if ((flags & F_REORDER) != 0)
{
if (prev)
builder.append(" | "); //$NON-NLS-1$
builder.append("REORDERED"); //$NON-NLS-1$
prev = true;
}
if ((flags & F_FINE_GRAINED) != 0)
{
if (prev)
builder.append(" | "); //$NON-NLS-1$
builder.append("FINE GRAINED"); //$NON-NLS-1$
prev = true;
}
if ((flags & F_OPEN) != 0)
{
if (prev)
builder.append(" | "); //$NON-NLS-1$
builder.append("OPEN"); //$NON-NLS-1$
prev = true;
}
if ((flags & F_DESCRIPTION) != 0)
{
if (prev)
builder.append(" | "); //$NON-NLS-1$
builder.append("DESCRIPTION"); //$NON-NLS-1$
prev = true;
}
if ((flags & F_WORKING_COPY) != 0)
{
if (prev)
builder.append(" | "); //$NON-NLS-1$
builder.append("WORKING COPY"); //$NON-NLS-1$
prev = true;
}
if ((flags & F_UNDERLYING_RESOURCE) != 0)
{
if (prev)
builder.append(" | "); //$NON-NLS-1$
builder.append("UNDERLYING RESOURCE"); //$NON-NLS-1$
prev = true;
}
if ((flags & F_MARKERS) != 0)
{
if (prev)
builder.append(" | "); //$NON-NLS-1$
builder.append("MARKERS"); //$NON-NLS-1$
prev = true;
}
if ((flags & F_SYNC) != 0)
{
if (prev)
builder.append(" | "); //$NON-NLS-1$
builder.append("SYNC"); //$NON-NLS-1$
prev = true;
}
return prev;
}
/**
* Returns a new, initially empty delta for the given element.
* <p>
* This implementation uses {@link ElementDelta.Factory} registered in the
* element's model context. If no delta factory is registered in the model
* context, a new instance of this class (i.e., <code>ElementDelta</code>)
* is returned.
* </p>
*
* @param element not <code>null</code>
* @return a new, initially empty delta for the given element
* (never <code>null</code>)
*/
protected ElementDelta newDelta_(IElement element)
{
Factory factory = Elements.getModelContext(element).get(Factory.class);
if (factory != null)
return factory.newDelta(element);
return new ElementDelta(element);
}
/**
* Returns whether an index needs to be used for child lookup.
*
* @return <code>true</code> if the child index needs to be used,
* and <code>false</code> otherwise
*/
protected boolean needsChildIndex_()
{
return affectedChildrenCounter >= 3;
}
/**
* Sets the kind of this delta.
* <p>
* This is a low-level mutator method. In particular, it is the caller's
* responsibility to ensure validity of the given value.
* </p>
*
* @param kind the delta kind
* @see #getKind_()
*/
protected void setKind_(int kind)
{
this.kind = kind;
}
/**
* Sets the flags for this delta.
* <p>
* This is a low-level mutator method. In particular, it is the caller's
* responsibility to ensure validity of the given value.
* </p>
*
* @param flags the delta flags
* @see #getFlags_()
*/
protected void setFlags_(long flags)
{
this.flags = flags;
}
/**
* Sets an element describing this delta's element before it was moved
* to its current location.
* <p>
* This is a low-level mutator method. In particular, it is the caller's
* responsibility to set appropriate kind and flags for this delta.
* </p>
*
* @param movedFromElement an element describing this delta's element
* before it was moved to its current location
* @see #getMovedFromElement_()
*/
protected void setMovedFromElement_(IElement movedFromElement)
{
this.movedFromElement = movedFromElement;
}
/**
* Sets an element describing this delta's element in its new location.
* <p>
* This is a low-level mutator method. In particular, it is the caller's
* responsibility to set appropriate kind and flags for this delta.
* </p>
*
* @param movedToElement an element describing this delta's element
* in its new location
* @see #getMovedToElement_()
*/
protected void setMovedToElement_(IElement movedToElement)
{
this.movedToElement = movedToElement;
}
/**
* Sets the marker deltas for this delta. Clients <b>must not</b> modify
* the given array afterwards.
* <p>
* This is a low-level mutator method. In particular, it is the caller's
* responsibility to set appropriate kind and flags for this delta.
* </p>
*
* @param markerDeltas the marker deltas
* @see #getMarkerDeltas_()
*/
protected void setMarkerDeltas_(IMarkerDelta[] markerDeltas)
{
this.markerDeltas = markerDeltas;
}
/**
* Sets the resource deltas for this delta. Clients <b>must not</b> modify
* the given array afterwards.
* <p>
* This is a low-level mutator method. In particular, it is the caller's
* responsibility to set appropriate kind and flags for this delta.
* </p>
*
* @param resourceDeltas the resource deltas
* @see #getResourceDeltas_()
*/
protected void setResourceDeltas_(IResourceDelta[] resourceDeltas)
{
this.resourceDeltas = resourceDeltas;
this.resourceDeltasCounter = (resourceDeltas != null)
? resourceDeltas.length : 0;
}
/**
* Adds the given resource delta to the collection of resource deltas
* of this delta if permitted by the current state of this delta. Note
* that in contrast to {@link #setResourceDeltas_(IResourceDelta[])
* setResourceDeltas_}, this method can change this delta's kind and flags
* as appropriate.
* <p>
* If the kind of this delta is <code>ADDED</code> or <code>REMOVED</code>,
* this implementation returns without adding the given resource delta;
* otherwise, it sets the kind of this delta to <code>CHANGED</code> and
* adds the <code>F_CONTENT</code> change flag.
* </p>
*
* @param resourceDelta the resource delta to add (not <code>null</code>)
* @see #getResourceDeltas_()
*/
protected void addResourceDelta_(IResourceDelta resourceDelta)
{
if (resourceDelta == null)
throw new IllegalArgumentException();
switch (getKind_())
{
case ADDED:
case REMOVED:
// no need to add a child if this parent is added or removed
return;
case CHANGED:
setFlags_(getFlags_() | F_CONTENT);
break;
default:
setKind_(CHANGED);
setFlags_(getFlags_() | F_CONTENT);
}
if (resourceDeltas == null)
{
resourceDeltas = new IResourceDelta[5];
resourceDeltas[resourceDeltasCounter++] = resourceDelta;
return;
}
if (resourceDeltas.length == resourceDeltasCounter)
{
// need a resize
System.arraycopy(resourceDeltas, 0, (resourceDeltas =
new IResourceDelta[resourceDeltasCounter * 2]), 0,
resourceDeltasCounter);
}
resourceDeltas[resourceDeltasCounter++] = resourceDelta;
}
/**
* Based on the given delta, creates a delta tree that can be directly
* parented by this delta and {@link #addAffectedChild_ adds} the created
* tree as an affected child of this delta.
* <p>
* Note that after calling <code>insertSubTree_(delta)</code>
* there is no guarantee that
* </p>
* <pre> findDelta_(d.getElement_()) == d</pre>
* <p>
* or even that
* </p>
* <pre> findDelta_(d.getElement_()) != null</pre>
* <p>
* for any delta <code>d</code> in the subtree <code>delta</code>
* (subtree root included). For example, if this delta tree already contains
* a delta for <code>d.getElement_()</code>, that delta will be {@link
* #mergeWith_(ElementDelta) merged} with <code>d</code>, which may even
* result in a logically empty delta, i.e., no delta for the element.
* </p>
*
* @param delta the delta to insert (not <code>null</code>)
* @throws IllegalArgumentException if the given delta cannot be rooted
* in this delta
*/
protected void insertSubTree_(ElementDelta delta)
{
addAffectedChild_(createDeltaTree(delta));
}
/**
* Adds the given delta as an affected child of this delta if permitted
* by the current state of this delta. If this delta already contains a
* child delta for the same element as the given delta, {@link #mergeWith_(
* ElementDelta) merges} the existing child delta with the given delta.
* Note that in contrast to {@link #setAffectedChildren_(ElementDelta[])
* setAffectedChildren_}, this method can change this delta's kind and flags
* as appropriate.
* <p>
* It is the caller's responsibility to ensure that the given delta can be
* directly parented by this delta.
* </p>
* <p>
* Note that after calling <code>addAffectedChild_(delta)</code>
* there is no guarantee that
* </p>
* <pre> findDelta_(d.getElement_()) == d</pre>
* <p>
* or even that
* </p>
* <pre> findDelta_(d.getElement_()) != null</pre>
* <p>
* for any delta <code>d</code> in the subtree <code>delta</code>
* (subtree root included).
* </p>
* <p>
* If the kind of this delta is <code>ADDED</code> or <code>REMOVED</code>,
* this implementation returns without adding the given delta; otherwise,
* it sets the kind of this delta to <code>CHANGED</code> and adds the
* <code>F_CHILDREN</code> change flag and, if this delta's element is an
* {@link ISourceElement}, the <code>F_FINE_GRAINED</code> change flag.
* </p>
*
* @param child the delta to add as an affected child (not <code>null</code>)
* @see #getAffectedChildren_()
* @see #insertSubTree_(ElementDelta)
*/
protected void addAffectedChild_(ElementDelta child)
{
switch (getKind_())
{
case ADDED:
case REMOVED:
// no need to add a child if this parent is added or removed
return;
case CHANGED:
setFlags_(getFlags_() | F_CHILDREN);
break;
default:
setKind_(CHANGED);
setFlags_(getFlags_() | F_CHILDREN);
}
// if a child delta is added to a source file delta or below,
// it's a fine grained delta
if (element instanceof ISourceElement)
{
setFlags_(getFlags_() | F_FINE_GRAINED);
}
Key key = new Key(child.element);
Integer index = indexOfChild(key);
if (index == null) // new affected child
{
addNewChild(child);
}
else
{
ElementDelta existingChild = affectedChildren[index];
boolean wasEmpty = existingChild.isEmpty_();
existingChild.mergeWith_(child);
if (!wasEmpty && existingChild.isEmpty_())
removeExistingChild(key, index);
}
}
/**
* Merges this delta with the given delta; the given delta is not modified
* in any way.
* <p>
* It is the caller's responsibility to ensure that the given delta pertains
* to the same element as this delta.
* </p>
* <p>
* This implementation implements merge semantics in terms of calls to
* {@link #copyFrom_(ElementDelta, boolean) copyFrom_}.
* </p>
*
* @param delta the delta to merge with (not <code>null</code>)
*/
protected void mergeWith_(ElementDelta delta)
{
switch (getKind_())
{
case ADDED:
switch (delta.getKind_())
{
case ADDED: // element was added then added -> it is added
case CHANGED: // element was added then changed -> it is added
return;
case REMOVED: // element was added then removed -> noop
copyFrom_(newDelta_(getElement_()), true);
return;
}
break;
case REMOVED:
switch (delta.getKind_())
{
case ADDED: // element was removed then added -> it is changed
ElementDelta newDelta = newDelta_(getElement_());
newDelta.setKind_(CHANGED);
newDelta.setFlags_(F_CONTENT);
copyFrom_(newDelta, true);
return;
case CHANGED: // element was removed then changed -> it is removed
case REMOVED: // element was removed then removed -> it is removed
return;
}
break;
case CHANGED:
switch (delta.getKind_())
{
case ADDED: // element was changed then added -> it is added
case REMOVED: // element was changed then removed -> it is removed
copyFrom_(delta, true);
return;
case CHANGED: // element was changed then changed -> it is changed
copyFrom_(delta, false);
return;
}
break;
default:
copyFrom_(delta, true);
return;
}
}
/**
* Copies data from the given delta to this delta; the given delta is not
* modified in any way.
* <p>
* It is the caller's responsibility to ensure that the given delta pertains
* to the same element as this delta.
* </p>
* <p>
* Subclasses that introduce new fields should consider extending this method.
* </p>
*
* @param delta the delta to copy data from (not <code>null</code>)
* @param init <code>true</code> if this delta needs to be completely
* (re-)initialized with data from the given delta; <code>false</code>
* if this delta needs to be augmented with data from the given delta
*/
protected void copyFrom_(ElementDelta delta, boolean init)
{
if (init)
{
setKind_(delta.getKind_());
setFlags_(delta.getFlags_());
setMovedFromElement_(delta.getMovedFromElement_());
setMovedToElement_(delta.getMovedToElement_());
setAffectedChildren_(delta.getAffectedChildren_());
setMarkerDeltas_(delta.getMarkerDeltas_());
setResourceDeltas_(delta.getResourceDeltas_());
}
else
{
if (delta.getKind_() == NO_CHANGE)
return;
if (getKind_() == NO_CHANGE)
setKind_(delta.getKind_());
else if (getKind_() != delta.getKind_())
throw new IllegalArgumentException();
for (int i = 0; i < delta.affectedChildrenCounter; i++)
{
addAffectedChild_(delta.affectedChildren[i]);
}
// add marker deltas if needed
if (delta.markerDeltas != null)
{
if (markerDeltas != null)
throw new AssertionError(
"Merge of marker deltas is not supported"); //$NON-NLS-1$
setMarkerDeltas_(delta.getMarkerDeltas_());
}
// add resource deltas if needed
for (int i = 0; i < delta.resourceDeltasCounter; i++)
{
addResourceDelta_(delta.resourceDeltas[i]);
}
// update flags
setFlags_(getFlags_() | delta.getFlags_());
}
}
/**
* Sets the affected children for this delta. Clients <b>must not</b> modify
* the given array afterwards.
* <p>
* This is a low-level mutator method. In particular, it is the caller's
* responsibility to set appropriate kind and flags for this delta.
* </p>
*
* @param children the affected children (not <code>null</code>)
* @see #getAffectedChildren_()
*/
protected void setAffectedChildren_(ElementDelta[] children)
{
if (children == null)
throw new IllegalArgumentException();
affectedChildren = children;
affectedChildrenCounter = children.length;
childIndex = null;
}
/*
* Based on the given delta, creates a delta tree that can be directly
* parented by this delta. Returns the root of the created delta tree.
*
* @param delta the delta to create a delta tree for (not <code>null</code>)
* @return the root of the created delta tree (never <code>null</code>)
* @throws IllegalArgumentException if the given delta cannot be rooted
* in this delta
*/
private ElementDelta createDeltaTree(ElementDelta delta)
{
ElementDelta childDelta = delta;
List<IElement> ancestors = getAncestors(delta.element);
if (ancestors == null)
{
IContext context = of(FORMAT_STYLE, SHORT);
throw new IllegalArgumentException(MessageFormat.format(
"Delta {0} cannot be rooted in {1}", delta.toString_( //$NON-NLS-1$
context), toString_(context)));
}
for (IElement ancestor : ancestors)
{
ElementDelta ancestorDelta = newDelta_(ancestor);
ancestorDelta.addAffectedChild_(childDelta);
childDelta = ancestorDelta;
}
return childDelta;
}
/*
* Returns a list of ancestor elements of the given element up to
* (but not including) the element of this delta in bottom-up order.
* If the given element is not a descendant of this delta's element,
* <code>null</code> is returned.
*
* @param child not <code>null</code>
* @return a list of ancestor elements of the given element up to
* (but not including) the element of this delta in bottom-up order,
* or <code>null</code> if the given element is not a descendant of
* this delta's element
*/
private List<IElement> getAncestors(IElement child)
{
IElement parent = Elements.getParent(child);
if (parent == null)
return null;
ArrayList<IElement> parents = new ArrayList<>();
while (!Elements.equalsAndSameParentChain(parent, element))
{
parents.add(parent);
parent = Elements.getParent(parent);
if (parent == null)
return null;
}
parents.trimToSize();
return parents;
}
/*
* Returns element deltas for all affected children of the given kind.
*
* @param kind one of <code>ADDED</code>, <code>REMOVED</code>, or
* <code>CHANGED</code>
* @return element deltas for all affected children of the given kind
* (never <code>null</code>)
*/
private ElementDelta[] getChildrenOfKind(int kind)
{
// note: the resulting array is to be of the runtime type of affectedChildren
if (affectedChildren.length == 0)
return affectedChildren;
ArrayList<ElementDelta> children = new ArrayList<>(
affectedChildrenCounter);
for (int i = 0; i < affectedChildrenCounter; i++)
{
ElementDelta child = affectedChildren[i];
if (child.getKind_() == kind)
children.add(child);
}
if (children.size() == affectedChildren.length)
return affectedChildren;
ElementDelta[] result = (ElementDelta[])Array.newInstance(
affectedChildren.getClass().getComponentType(), children.size());
return children.toArray(result);
}
/*
* Returns the descendant delta for the given key,
* or <code>null</code> if no delta is found for the given key.
*
* @param key the key to search delta for (not <code>null</code>)
* @return the descendant delta for the given key,
* or <code>null</code> if none
*/
private ElementDelta findDescendant(Key key)
{
if (affectedChildrenCounter == 0 || !Elements.isAncestorOf(element,
Elements.getParent(key.element)))
return null;
Integer index = indexOfChild(key);
if (index != null)
return affectedChildren[index];
for (int i = 0; i < affectedChildrenCounter; i++)
{
ElementDelta delta = affectedChildren[i].findDescendant(key);
if (delta != null)
return delta;
}
return null;
}
/*
* Given a delta key, returns the index of the delta in the list of
* affected children, or <code>null</code> if no child delta is found
* for the given key.
*
* @param key the key to search child delta for (not <code>null</code>)
* @return the index of the child delta for the given key,
* or <code>null</code> if not found
*/
private Integer indexOfChild(Key key)
{
if (!needsChildIndex_())
{
for (int i = 0; i < affectedChildrenCounter; i++)
{
if (Elements.equalsAndSameParentChain(key.element,
affectedChildren[i].element))
{
return i;
}
}
return null;
}
if (childIndex == null)
{
childIndex = new HashMap<Key, Integer>();
for (int i = 0; i < affectedChildrenCounter; i++)
{
childIndex.put(new Key(affectedChildren[i].element), i);
}
}
return childIndex.get(key);
}
/*
* Adds the given delta as a new affected child of this delta without
* any checks.
* <p>
* It is the caller's responsibility to ensure that this delta doesn't
* already contain a child delta for the same element as the given delta
* and that the given delta can be a direct child of this delta.
* </p>
*
* @param child the delta to add as a new affected child
* (not <code>null</code>)
*/
private void addNewChild(ElementDelta child)
{
int length = affectedChildren.length;
if (length == affectedChildrenCounter)
{
// need a resize
int newLength = (length == 0 ? 1 : length * 2);
// be careful to preserve the runtime type of affectedChildren
affectedChildren = Arrays.copyOf(affectedChildren, newLength);
}
affectedChildren[affectedChildrenCounter++] = child;
if (childIndex != null)
{
childIndex.put(new Key(child.element), affectedChildrenCounter - 1);
}
}
/*
* Removes the specified child delta from the list of affected children.
*
* @param key
* the key of the child delta (not <code>null</code>)
* @param index
* the index of the child delta in the list of affected children
*/
private void removeExistingChild(Key key, int index)
{
if (affectedChildren.length == affectedChildrenCounter)
{
// don't mutate the original array: it may be shared
affectedChildren = affectedChildren.clone();
}
int rest = affectedChildrenCounter - index - 1;
if (rest > 0)
{
System.arraycopy(affectedChildren, index + 1, affectedChildren,
index, rest);
}
affectedChildren[--affectedChildrenCounter] = null;
if (childIndex != null)
{
if (!needsChildIndex_())
childIndex = null;
else
{
childIndex.remove(key);
for (int i = index; i < affectedChildrenCounter; i++)
{
childIndex.put(new Key(affectedChildren[i].element), i);
}
}
}
}
/**
* Represents a factory for creating instances of {@link ElementDelta}.
*/
public interface Factory
{
/**
* Returns a new, initially empty delta for the given element.
*
* @param element not <code>null</code>
* @return a new, initially empty delta for the given element
* (never <code>null</code>)
*/
ElementDelta newDelta(IElement element);
}
/**
* Builds a tree of {@link ElementDelta} objects based on elementary changes.
*/
public static class Builder
implements IElementDeltaBuilder
{
private final ElementDelta rootDelta;
/**
* Constructs a delta tree builder on the given root delta.
*
* @param rootDelta not <code>null</code>
*/
public Builder(ElementDelta rootDelta)
{
if (rootDelta == null)
throw new IllegalArgumentException();
this.rootDelta = rootDelta;
}
/**
* Returns the root delta instance specified in the constructor.
*
* @return the root delta instance (never <code>null</code>)
*/
@Override
public ElementDelta getDelta()
{
return rootDelta;
}
@Override
public Builder added(IElement element)
{
return added(element, 0);
}
@Override
public Builder added(IElement element, long flags)
{
insert(newAdded(element, flags));
return this;
}
@Override
public Builder removed(IElement element)
{
return removed(element, 0);
}
@Override
public Builder removed(IElement element, long flags)
{
insert(newRemoved(element, flags));
return this;
}
@Override
public Builder changed(IElement element, long flags)
{
insert(newChanged(element, flags));
return this;
}
@Override
public Builder movedFrom(IElement movedFromElement,
IElement movedToElement)
{
insert(newMovedFrom(movedFromElement, movedToElement));
return this;
}
@Override
public Builder movedTo(IElement movedToElement,
IElement movedFromElement)
{
insert(newMovedTo(movedToElement, movedFromElement));
return this;
}
@Override
public Builder markersChanged(IElement element,
IMarkerDelta[] markerDeltas)
{
if (markerDeltas == null || markerDeltas.length == 0)
throw new IllegalArgumentException();
ElementDelta delta = newChanged(element, F_MARKERS);
delta.setMarkerDeltas_(markerDeltas);
insert(delta);
return this;
}
@Override
public Builder addResourceDelta(IElement element,
IResourceDelta resourceDelta)
{
if (resourceDelta == null)
throw new IllegalArgumentException();
ElementDelta delta = newChanged(element, F_CONTENT);
delta.addResourceDelta_(resourceDelta);
insert(delta);
return this;
}
private void insert(ElementDelta delta)
{
if (!Elements.equalsAndSameParentChain(rootDelta.element,
delta.element))
rootDelta.insertSubTree_(delta);
else
rootDelta.mergeWith_(delta);
}
/*
* Returns a new <code>ADDED</code> delta for the given element.
*
* @param element the element that this delta describes a change to
* (not <code>null</code>)
* @param flags delta flags
* @return a new <code>ADDED</code> delta for the given element
* (never <code>null</code>)
*/
private ElementDelta newAdded(IElement element, long flags)
{
ElementDelta delta = rootDelta.newDelta_(element);
delta.setKind_(ADDED);
delta.setFlags_(flags);
return delta;
}
/*
* Returns a new <code>REMOVED</code> delta for the given element.
*
* @param element the element that this delta describes a change to
* (not <code>null</code>)
* @param flags delta flags
* @return a new <code>REMOVED</code> delta for the given element
* (never <code>null</code>)
*/
private ElementDelta newRemoved(IElement element, long flags)
{
ElementDelta delta = rootDelta.newDelta_(element);
delta.setKind_(REMOVED);
delta.setFlags_(flags);
return delta;
}
/*
* Returns a new <code>CHANGED</code> delta for the given element.
*
* @param element the element that this delta describes a change to
* (not <code>null</code>)
* @param flags delta flags
* @return a new <code>CHANGED</code> delta for the given element
* (never <code>null</code>)
*/
private ElementDelta newChanged(IElement element, long flags)
{
ElementDelta delta = rootDelta.newDelta_(element);
delta.setKind_(CHANGED);
delta.setFlags_(flags);
return delta;
}
/*
* Returns a new "moved from" (<code>REMOVED</code>) delta for the
* given element.
*
* @param movedFromElement the element before it was moved to its
* current location (not <code>null</code>)
* @param movedToElement the element in its new location
* (not <code>null</code>)
* @return a new "moved from" (<code>REMOVED</code>) delta
* (never <code>null</code>)
*/
private ElementDelta newMovedFrom(IElement movedFromElement,
IElement movedToElement)
{
ElementDelta delta = newRemoved(movedFromElement, F_MOVED_TO);
if (movedToElement == null)
throw new IllegalArgumentException();
delta.setMovedToElement_(movedToElement);
return delta;
}
/*
* Returns a new "moved to" (<code>ADDED</code>) delta for the given
* element.
*
* @param movedToElement the element in its new location
* (not <code>null</code>)
* @param movedFromElement the element before it was moved to its
* current location (not <code>null</code>)
* @return a new "moved to" (<code>ADDED</code>) delta
* (never <code>null</code>)
*/
private ElementDelta newMovedTo(IElement movedToElement,
IElement movedFromElement)
{
ElementDelta delta = newAdded(movedToElement, F_MOVED_FROM);
if (movedFromElement == null)
throw new IllegalArgumentException();
delta.setMovedFromElement_(movedFromElement);
return delta;
}
}
/*
* Represents a delta key.
* @see ElementDelta#childIndex
*/
private static class Key
{
final IElement element;
/*
* Constructs a new delta key for the given element.
*
* @param element not <code>null</code>
*/
Key(IElement element)
{
if (element == null)
throw new IllegalArgumentException();
this.element = element;
}
@Override
public int hashCode()
{
return element.hashCode();
}
@Override
public boolean equals(Object obj)
{
if (!(obj instanceof Key))
return false;
return Elements.equalsAndSameParentChain(element,
((Key)obj).element);
}
}
}