blob: 210d33518f162e9613c74d24631c1b3bc0b0b528 [file] [log] [blame]
/*
* Copyright (c) 2008-2015 Eike Stepper (Berlin, Germany) 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
*
* Contributors:
* Simon McDuff - initial API and implementation
* Eike Stepper - maintenance
* Cyril Jaquier - Bug 310574 (with the help of Pascal Lehmann)
*/
package org.eclipse.emf.cdo.internal.common.revision.delta;
import org.eclipse.emf.cdo.common.protocol.CDODataInput;
import org.eclipse.emf.cdo.common.protocol.CDODataOutput;
import org.eclipse.emf.cdo.common.revision.CDORevision;
import org.eclipse.emf.cdo.common.revision.delta.CDOAddFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDOFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDOFeatureDeltaVisitor;
import org.eclipse.emf.cdo.common.revision.delta.CDOListFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDOMoveFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDORemoveFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDOSetFeatureDelta;
import org.eclipse.emf.cdo.spi.common.revision.CDOReferenceAdjuster;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDOFeatureDelta;
import org.eclipse.net4j.util.ObjectUtil;
import org.eclipse.net4j.util.collection.Pair;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.FeatureMapUtil;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
/**
* @author Simon McDuff
*/
public class CDOListFeatureDeltaImpl extends CDOFeatureDeltaImpl implements CDOListFeatureDelta
{
private final int originSize;
private final List<CDOFeatureDelta> listChanges = new ArrayList<CDOFeatureDelta>();
private transient int[] cachedIndices;
private transient ListTargetAdding[] cachedSources;
private transient List<CDOFeatureDelta> unprocessedFeatureDeltas;
public CDOListFeatureDeltaImpl(EStructuralFeature feature, int originSize)
{
super(feature);
this.originSize = originSize;
}
public CDOListFeatureDeltaImpl(CDODataInput in, EClass eClass) throws IOException
{
super(in, eClass);
originSize = in.readInt();
int size = in.readInt();
for (int i = 0; i < size; i++)
{
listChanges.add(in.readCDOFeatureDelta(eClass));
}
}
public CDOListFeatureDelta copy()
{
CDOListFeatureDeltaImpl result = new CDOListFeatureDeltaImpl(getFeature(), getOriginSize());
Map<CDOFeatureDelta, CDOFeatureDelta> map = null;
if (cachedSources != null || unprocessedFeatureDeltas != null)
{
map = new HashMap<CDOFeatureDelta, CDOFeatureDelta>();
}
for (CDOFeatureDelta delta : listChanges)
{
CDOFeatureDelta newDelta = delta.copy();
result.listChanges.add(newDelta);
if (map != null)
{
map.put(delta, newDelta);
}
}
if (cachedIndices != null)
{
result.cachedIndices = copyOf(cachedIndices, cachedIndices.length);
}
if (cachedSources != null)
{
int length = cachedSources.length;
result.cachedSources = new ListTargetAdding[length];
for (int i = 0; i < length; i++)
{
ListTargetAdding oldElement = cachedSources[i];
CDOFeatureDelta newElement = map.get(oldElement);
if (newElement instanceof ListTargetAdding)
{
result.cachedSources[i] = (ListTargetAdding)newElement;
}
}
}
if (unprocessedFeatureDeltas != null)
{
int size = unprocessedFeatureDeltas.size();
result.unprocessedFeatureDeltas = new ArrayList<CDOFeatureDelta>(size);
for (CDOFeatureDelta oldDelta : unprocessedFeatureDeltas)
{
CDOFeatureDelta newDelta = map.get(oldDelta);
if (newDelta != null)
{
result.unprocessedFeatureDeltas.add(newDelta);
}
}
}
return result;
}
@Override
public void write(CDODataOutput out, EClass eClass) throws IOException
{
super.write(out, eClass);
out.writeInt(originSize);
out.writeInt(listChanges.size());
for (CDOFeatureDelta featureDelta : listChanges)
{
out.writeCDOFeatureDelta(eClass, featureDelta);
}
}
public Type getType()
{
return Type.LIST;
}
public int getOriginSize()
{
return originSize;
}
public List<CDOFeatureDelta> getListChanges()
{
return listChanges;
}
/**
* Returns the number of indices as the first element of the array.
*
* @return never <code>null</code>.
*/
public Pair<ListTargetAdding[], int[]> reconstructAddedIndices()
{
reconstructAddedIndicesWithNoCopy();
return Pair.create(copyOf(cachedSources, cachedSources.length, cachedSources.getClass()),
copyOf(cachedIndices, cachedIndices.length));
}
private void reconstructAddedIndicesWithNoCopy()
{
// Note that cachedIndices and cachedSources are always either both null or
// both non-null, and in the latter case, are always of the same length.
// Furthermore, there can only be unprocessedFeatureDeltas if cachesIndices
// and cachedSources are non-null.
if (cachedIndices == null || unprocessedFeatureDeltas != null)
{
if (cachedIndices == null)
{
int initialCapacity = listChanges.size() + 1;
cachedIndices = new int[initialCapacity];
cachedSources = new ListTargetAdding[initialCapacity];
}
else
// i.e. unprocessedFeatureDeltas != null
{
int requiredCapacity = 1 + cachedIndices[0] + unprocessedFeatureDeltas.size();
if (cachedIndices.length < requiredCapacity)
{
int newCapacity = Math.max(requiredCapacity, cachedIndices.length * 2);
int[] newIndices = new int[newCapacity];
System.arraycopy(cachedIndices, 0, newIndices, 0, cachedIndices.length);
cachedIndices = newIndices;
ListTargetAdding[] newSources = new ListTargetAdding[newCapacity];
System.arraycopy(cachedSources, 0, newSources, 0, cachedSources.length);
cachedSources = newSources;
}
}
List<CDOFeatureDelta> featureDeltasToBeProcessed = unprocessedFeatureDeltas == null ? listChanges
: unprocessedFeatureDeltas;
for (CDOFeatureDelta featureDelta : featureDeltasToBeProcessed)
{
if (featureDelta instanceof ListIndexAffecting)
{
ListIndexAffecting affecting = (ListIndexAffecting)featureDelta;
affecting.affectIndices(cachedSources, cachedIndices);
}
if (featureDelta instanceof ListTargetAdding)
{
cachedIndices[++cachedIndices[0]] = ((ListTargetAdding)featureDelta).getIndex();
cachedSources[cachedIndices[0]] = (ListTargetAdding)featureDelta;
}
}
unprocessedFeatureDeltas = null;
}
}
private boolean cleanupWithNewDelta(CDOFeatureDelta featureDelta)
{
EStructuralFeature feature = getFeature();
switch (featureDelta.getType())
{
case REMOVE:
{
if (feature instanceof EReference || FeatureMapUtil.isFeatureMap(feature))
{
Boolean result = cleanupWithNewRemoveDelta((CDORemoveFeatureDelta)featureDelta);
if (result != null)
{
return result;
}
}
break;
}
case SET:
{
Boolean result = cleanupWithNewSetDelta((CDOSetFeatureDelta)featureDelta);
if (result != null)
{
return result;
}
break;
}
}
if (cachedIndices != null)
{
if (unprocessedFeatureDeltas == null)
{
unprocessedFeatureDeltas = new ArrayList<CDOFeatureDelta>();
}
unprocessedFeatureDeltas.add(featureDelta);
}
return true;
}
private Boolean cleanupWithNewRemoveDelta(CDORemoveFeatureDelta removeFeatureDelta)
{
int indexToRemove = removeFeatureDelta.getIndex();
reconstructAddedIndicesWithNoCopy();
for (int i = 1; i <= cachedIndices[0]; i++)
{
int index = cachedIndices[i];
if (indexToRemove == index)
{
// The previous implementation set the value of the feature delta to CDOID.NULL. Databinding and probably
// others don't really like it. We now remove the ADD (or SET which seems to appear in CDOListFeatureDelta
// during opposite adjustment!? Why???) and patch the other feature deltas.
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=310574
ListTargetAdding delta = cachedSources[i];
// We use a "floating" index which is the index (in the list) of the item to remove at the time when the
// object was still in the list. This index evolves with the feature deltas.
int floatingIndex = delta.getIndex();
// First updates cachedSources and cachedIndices using CDORemoveFeatureDelta.
ListIndexAffecting affecting = (ListIndexAffecting)removeFeatureDelta;
affecting.affectIndices(cachedSources, cachedIndices);
// Then adjusts the remaining feature deltas.
boolean skip = true;
ListIterator<CDOFeatureDelta> iterator = listChanges.listIterator();
while (iterator.hasNext())
{
CDOFeatureDelta fd = iterator.next();
// We only need to process feature deltas that come after the ADD (or SET) to be removed.
if (skip)
{
if (fd == delta)
{
// Found the ADD (or SET) feature delta that we need to remove. So remove it from the list and start
// processing the next feature deltas.
skip = false;
iterator.remove();
// SET
if (fd instanceof CDOSetFeatureDelta)
{
// If the removed delta is SET we add the REMOVE to the feature deltas. We do not need to adjust the
// other feature deltas because SET do not modify the list.
return true;
}
}
continue;
}
// ADD
if (fd instanceof CDOAddFeatureDelta)
{
// Increases the floating index if the ADD came in front of the item.
if (((CDOAddFeatureDelta)fd).getIndex() <= floatingIndex)
{
++floatingIndex;
}
// Adjusts the feature delta too.
((WithIndex)fd).adjustAfterRemoval(floatingIndex);
}
// REMOVE
else if (fd instanceof CDORemoveFeatureDelta)
{
int idx = floatingIndex;
// Decreases the floating index if the REMOVE came in front of the item.
if (((CDORemoveFeatureDelta)fd).getIndex() <= floatingIndex)
{
--floatingIndex;
}
// Adjusts the feature delta too.
((WithIndex)fd).adjustAfterRemoval(idx);
}
// MOVE
else if (fd instanceof CDOMoveFeatureDelta)
{
// Remembers the positions before we patch them.
int from = ((CDOMoveFeatureDelta)fd).getOldPosition();
int to = ((CDOMoveFeatureDelta)fd).getNewPosition();
if (floatingIndex == from)
{
// We are moving the "to be deleted" item. So we update our floating index and remove the MOVE. It has
// no effect on the list.
floatingIndex = to;
iterator.remove();
}
else
{
// In the other cases, we need to patch the positions.
// If the old position is greater or equal to the current position of the item to be removed (remember,
// that's our floating index), decrease the position.
int patchedFrom = floatingIndex <= from ? from - 1 : from;
// The new position requires more care. We need to know the direction of the move (left-to-right or
// right-to-left).
int patchedTo;
if (from > to)
{
// left-to-right. Only decreases the position if it is strictly greater than the current item
// position.
patchedTo = floatingIndex < to ? to - 1 : to;
}
else
{
// right-to-left. Decreases the position if it is greater or equal than the current item position.
patchedTo = floatingIndex <= to ? to - 1 : to;
}
// We can now update our floating index. We use the original positions because the floating index
// represents the item "to be deleted" before it was actually removed.
if (from < floatingIndex && floatingIndex <= to)
{
--floatingIndex;
}
else if (to <= floatingIndex && floatingIndex < from)
{
++floatingIndex;
}
// And finally adjust the feature delta.
if (patchedFrom == patchedTo)
{
// Source and destination are the same so just remove the feature delta.
iterator.remove();
}
else
{
((CDOMoveFeatureDeltaImpl)fd).setOldPosition(patchedFrom);
((CDOMoveFeatureDeltaImpl)fd).setNewPosition(patchedTo);
}
}
}
// SET
else if (fd instanceof CDOSetFeatureDelta)
{
// Adjusts the feature delta too.
((WithIndex)fd).adjustAfterRemoval(floatingIndex);
}
}
// If the removed delta was ADD so we do not add the REMOVE to the feature deltas.
return false;
}
}
return null;
}
/**
* A new SET feature delta can interfere with former ADD or SET deltas.
*/
private Boolean cleanupWithNewSetDelta(CDOSetFeatureDelta featureDelta)
{
final class DeltaProxy implements InternalCDOFeatureDelta.WithIndex
{
private final int indexIntoListChanges;
private int index;
public DeltaProxy(int indexIntoListChanges, int index)
{
this.indexIntoListChanges = indexIntoListChanges;
this.index = index;
}
public int getIndexIntoListChanges()
{
return indexIntoListChanges;
}
public int getIndex()
{
return index;
}
public void adjustAfterAddition(int index)
{
if (index <= this.index)
{
++this.index;
}
}
public void adjustAfterRemoval(int index)
{
if (index < this.index && this.index > 0)
{
--this.index;
}
}
}
int size = listChanges.size();
List<DeltaProxy> proxies = new ArrayList<DeltaProxy>();
for (int i = 0; i < size; i++)
{
CDOFeatureDelta fd = listChanges.get(i);
switch (fd.getType())
{
case MOVE:
{
int oldPosition = ((CDOMoveFeatureDelta)fd).getOldPosition();
int newPosition = ((CDOMoveFeatureDelta)fd).getNewPosition();
for (DeltaProxy proxy : proxies)
{
proxy.adjustAfterRemoval(oldPosition);
proxy.adjustAfterAddition(newPosition);
}
break;
}
case REMOVE:
{
int index = ((CDORemoveFeatureDelta)fd).getIndex();
for (DeltaProxy proxy : proxies)
{
proxy.adjustAfterRemoval(index);
}
break;
}
case ADD:
{
int index = ((CDOAddFeatureDelta)fd).getIndex();
for (DeltaProxy proxy : proxies)
{
proxy.adjustAfterAddition(index);
}
proxies.add(new DeltaProxy(i, index));
break;
}
case SET:
{
int index = ((CDOSetFeatureDelta)fd).getIndex();
proxies.add(new DeltaProxy(i, index));
break;
}
}
}
int index = featureDelta.getIndex();
for (DeltaProxy proxy : proxies)
{
if (proxy.getIndex() == index)
{
int indexIntoListChanges = proxy.getIndexIntoListChanges();
CDOFeatureDelta featureDeltaToModify = listChanges.get(indexIntoListChanges);
if (featureDeltaToModify.getType() == CDOFeatureDelta.Type.ADD)
{
((CDOAddFeatureDeltaImpl)featureDeltaToModify).setValue(featureDelta.getValue());
return false;
}
// Here featureDeltaToModify is a SET delta
listChanges.remove(indexIntoListChanges); // Replace the SET delta that existed for this index
break;
}
}
return null;
}
public void add(CDOFeatureDelta featureDelta)
{
// Only adds the feature delta to the list if required.
if (cleanupWithNewDelta(featureDelta))
{
listChanges.add(featureDelta);
}
}
public Object applyTo(CDORevision revision)
{
for (CDOFeatureDelta featureDelta : listChanges)
{
((CDOFeatureDeltaImpl)featureDelta).applyTo(revision);
}
return null;
}
@Override
public boolean adjustReferences(CDOReferenceAdjuster adjuster)
{
boolean changed = false;
for (CDOFeatureDelta featureDelta : listChanges)
{
changed |= ((CDOFeatureDeltaImpl)featureDelta).adjustReferences(adjuster);
}
return changed;
}
public void accept(CDOFeatureDeltaVisitor visitor)
{
visitor.visit(this);
}
@Override
public boolean isStructurallyEqual(Object obj)
{
if (!super.isStructurallyEqual(obj))
{
return false;
}
CDOListFeatureDelta that = (CDOListFeatureDelta)obj;
return ObjectUtil.equals(listChanges, that.getListChanges());
}
@Override
protected String toStringAdditional()
{
return "originSize=" + originSize + ", list=" + listChanges; //$NON-NLS-1$
}
/**
* Copied from JAVA 1.6 {@link Arrays Arrays.copyOf}.
*/
private static <T, U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType)
{
@SuppressWarnings("unchecked")
T[] copy = (Object)newType == (Object)Object[].class ? (T[])new Object[newLength]
: (T[])Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
/**
* Copied from JAVA 1.6 {@link Arrays Arrays.copyOf}.
*/
private static int[] copyOf(int[] original, int newLength)
{
int[] copy = new int[newLength];
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
}