/*
* Copyright (c) 2008-2014, 2016 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:
* Eike Stepper - initial API and implementation
* Simon McDuff - bug 201266
* Simon McDuff - bug 204890
*/
package org.eclipse.emf.cdo.internal.common.revision.delta;
import org.eclipse.emf.cdo.common.branch.CDOBranch;
import org.eclipse.emf.cdo.common.branch.CDOBranchManager;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.common.id.CDOIDUtil;
import org.eclipse.emf.cdo.common.id.CDOWithID;
import org.eclipse.emf.cdo.common.protocol.CDODataInput;
import org.eclipse.emf.cdo.common.protocol.CDODataOutput;
import org.eclipse.emf.cdo.common.revision.CDOElementProxy;
import org.eclipse.emf.cdo.common.revision.CDOList;
import org.eclipse.emf.cdo.common.revision.CDORevisable;
import org.eclipse.emf.cdo.common.revision.CDORevision;
import org.eclipse.emf.cdo.common.revision.CDORevisionData;
import org.eclipse.emf.cdo.common.revision.CDORevisionUtil;
import org.eclipse.emf.cdo.common.revision.delta.CDOClearFeatureDelta;
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.CDOOriginSizeProvider;
import org.eclipse.emf.cdo.common.revision.delta.CDORevisionDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDOSetFeatureDelta;
import org.eclipse.emf.cdo.common.revision.delta.CDOUnsetFeatureDelta;
import org.eclipse.emf.cdo.common.util.PartialCollectionLoadingNotSupportedException;
import org.eclipse.emf.cdo.internal.common.revision.CDOListImpl;
import org.eclipse.emf.cdo.spi.common.branch.CDOBranchAdjustable;
import org.eclipse.emf.cdo.spi.common.revision.CDOReferenceAdjuster;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDOFeatureDelta;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionDelta;
import org.eclipse.net4j.util.Predicate;
import org.eclipse.net4j.util.Predicates;
import org.eclipse.emf.common.util.ECollections;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.change.ListChange;
import org.eclipse.emf.ecore.change.util.ListDifferenceAnalyzer;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
/**
* @author Eike Stepper
*/
public class CDORevisionDeltaImpl implements InternalCDORevisionDelta
{
private EClass eClass;
private CDOID id;
private CDOBranch branch;
private int version;
private CDORevisable target;
private Map<EStructuralFeature, CDOFeatureDelta> featureDeltas = new HashMap<EStructuralFeature, CDOFeatureDelta>();
public CDORevisionDeltaImpl(CDORevision revision)
{
eClass = revision.getEClass();
id = revision.getID();
branch = revision.getBranch();
version = revision.getVersion();
}
public CDORevisionDeltaImpl(CDORevisionDelta revisionDelta, boolean copyFeatureDeltas)
{
eClass = revisionDelta.getEClass();
id = revisionDelta.getID();
branch = revisionDelta.getBranch();
version = revisionDelta.getVersion();
if (copyFeatureDeltas)
{
for (CDOFeatureDelta delta : revisionDelta.getFeatureDeltas())
{
CDOFeatureDelta copy = ((InternalCDOFeatureDelta)delta).copy();
addFeatureDelta(copy, null);
}
}
}
public CDORevisionDeltaImpl(CDORevision sourceRevision, CDORevision targetRevision)
{
if (sourceRevision.getEClass() != targetRevision.getEClass())
{
throw new IllegalArgumentException();
}
eClass = sourceRevision.getEClass();
id = sourceRevision.getID();
branch = sourceRevision.getBranch();
version = sourceRevision.getVersion();
target = CDORevisionUtil.copyRevisable(targetRevision);
compare((InternalCDORevision)sourceRevision, (InternalCDORevision)targetRevision);
CDORevisionData originData = sourceRevision.data();
CDORevisionData dirtyData = targetRevision.data();
Object dirtyContainerID = dirtyData.getContainerID();
if (dirtyContainerID instanceof CDOWithID)
{
dirtyContainerID = ((CDOWithID)dirtyContainerID).cdoID();
}
CDOID dirtyResourceID = dirtyData.getResourceID();
int dirtyContainingFeatureID = dirtyData.getContainingFeatureID();
if (!compareValue(originData.getContainerID(), dirtyContainerID) || !compareValue(originData.getContainingFeatureID(), dirtyContainingFeatureID)
|| !compareValue(originData.getResourceID(), dirtyResourceID))
{
CDOFeatureDelta delta = new CDOContainerFeatureDeltaImpl(dirtyResourceID, dirtyContainerID, dirtyContainingFeatureID);
addFeatureDelta(delta, null);
}
}
public CDORevisionDeltaImpl(CDODataInput in) throws IOException
{
eClass = (EClass)in.readCDOClassifierRefAndResolve();
id = in.readCDOID();
branch = in.readCDOBranch();
version = in.readInt();
if (version < 0)
{
version = -version;
target = in.readCDORevisable();
}
int size = in.readInt();
for (int i = 0; i < size; i++)
{
CDOFeatureDelta featureDelta = in.readCDOFeatureDelta(eClass);
featureDeltas.put(featureDelta.getFeature(), featureDelta);
}
}
public void write(CDODataOutput out) throws IOException
{
out.writeCDOClassifierRef(eClass);
out.writeCDOID(id);
out.writeCDOBranch(branch);
if (target == null)
{
out.writeInt(version);
}
else
{
out.writeInt(-version);
out.writeCDORevisable(target);
}
out.writeInt(featureDeltas.size());
for (CDOFeatureDelta featureDelta : featureDeltas.values())
{
out.writeCDOFeatureDelta(eClass, featureDelta);
}
}
public EClass getEClass()
{
return eClass;
}
public CDOID getID()
{
return id;
}
public CDOBranch getBranch()
{
return branch;
}
public void setBranch(CDOBranch branch)
{
this.branch = branch;
}
public int getVersion()
{
return version;
}
public void setVersion(int version)
{
this.version = version;
}
public CDORevisable getTarget()
{
return target;
}
public void setTarget(CDORevisable target)
{
this.target = target;
}
public int size()
{
return featureDeltas.size();
}
public boolean isEmpty()
{
return featureDeltas.isEmpty();
}
public CDORevisionDelta copy()
{
return new CDORevisionDeltaImpl(this, true);
}
public Map<EStructuralFeature, CDOFeatureDelta> getFeatureDeltaMap()
{
return featureDeltas;
}
public CDOFeatureDelta getFeatureDelta(EStructuralFeature feature)
{
return featureDeltas.get(feature);
}
public List<CDOFeatureDelta> getFeatureDeltas()
{
return new ArrayList<CDOFeatureDelta>(featureDeltas.values());
}
@Deprecated
public void apply(CDORevision revision)
{
applyTo(revision);
}
public void applyTo(CDORevision revision)
{
for (CDOFeatureDelta featureDelta : featureDeltas.values())
{
((CDOFeatureDeltaImpl)featureDelta).applyTo(revision);
}
}
@Deprecated
public void addFeatureDelta(CDOFeatureDelta delta)
{
throw new UnsupportedOperationException();
}
public void addFeatureDelta(CDOFeatureDelta delta, CDOOriginSizeProvider originSizeProvider)
{
if (delta instanceof CDOListFeatureDelta)
{
CDOListFeatureDelta listDelta = (CDOListFeatureDelta)delta;
for (CDOFeatureDelta listChange : listDelta.getListChanges())
{
addSingleFeatureDelta(listChange, listDelta);
}
}
else
{
addSingleFeatureDelta(delta, originSizeProvider);
}
}
private void addSingleFeatureDelta(CDOFeatureDelta delta, CDOOriginSizeProvider originSizeProvider)
{
EStructuralFeature feature = delta.getFeature();
if (feature.isMany())
{
CDOListFeatureDeltaImpl listDelta = (CDOListFeatureDeltaImpl)featureDeltas.get(feature);
if (listDelta == null)
{
int originSize = originSizeProvider.getOriginSize();
listDelta = new CDOListFeatureDeltaImpl(feature, originSize);
featureDeltas.put(feature, listDelta);
}
// Remove all previous changes
if (delta instanceof CDOClearFeatureDelta || delta instanceof CDOUnsetFeatureDelta)
{
listDelta.getListChanges().clear();
}
listDelta.add(delta);
}
else
{
CDOFeatureDelta oldDelta = featureDeltas.put(feature, delta);
if (oldDelta instanceof CDOSetFeatureDelta && delta instanceof CDOSetFeatureDelta)
{
Object oldValue = ((CDOSetFeatureDelta)oldDelta).getOldValue();
CDOSetFeatureDeltaImpl newDelta = (CDOSetFeatureDeltaImpl)delta;
newDelta.setOldValue(oldValue);
}
}
}
public boolean adjustReferences(CDOReferenceAdjuster referenceAdjuster)
{
boolean changed = false;
for (CDOFeatureDelta featureDelta : featureDeltas.values())
{
changed |= ((CDOFeatureDeltaImpl)featureDelta).adjustReferences(referenceAdjuster);
}
return changed;
}
public void adjustBranches(CDOBranchManager newBranchManager)
{
if (branch != null)
{
branch = newBranchManager.getBranch(branch.getID());
}
if (target instanceof CDOBranchAdjustable)
{
((CDOBranchAdjustable)target).adjustBranches(newBranchManager);
}
}
public void accept(CDOFeatureDeltaVisitor visitor)
{
accept(visitor, Predicates.<EStructuralFeature> alwaysTrue());
}
public void accept(CDOFeatureDeltaVisitor visitor, Predicate<EStructuralFeature> filter)
{
for (CDOFeatureDelta featureDelta : featureDeltas.values())
{
EStructuralFeature feature = featureDelta.getFeature();
if (filter.apply(feature))
{
((CDOFeatureDeltaImpl)featureDelta).accept(visitor);
}
}
}
private void compare(final InternalCDORevision originRevision, final InternalCDORevision dirtyRevision)
{
CDORevisionData originData = originRevision.data();
CDORevisionData dirtyData = dirtyRevision.data();
for (final EStructuralFeature feature : originRevision.getClassInfo().getAllPersistentFeatures())
{
if (feature.isMany())
{
final int originSize = originData.size(feature);
if (originSize > 0 && dirtyData.size(feature) == 0)
{
addFeatureDelta(new CDOClearFeatureDeltaImpl(feature), new CDOOriginSizeProvider()
{
public int getOriginSize()
{
return originSize;
}
});
}
else
{
CDOListFeatureDelta listFeatureDelta = new CDOListFeatureDeltaImpl(feature, originSize);
final List<CDOFeatureDelta> changes = listFeatureDelta.getListChanges();
ListDifferenceAnalyzer analyzer = new ListDifferenceAnalyzer()
{
@Override
public void analyzeLists(EList<Object> oldList, EList<?> newList, EList<ListChange> listChanges)
{
checkNoProxies(oldList, originRevision);
checkNoProxies(newList, dirtyRevision);
super.analyzeLists(oldList, newList, listChanges);
}
@Override
protected void createAddListChange(EList<Object> oldList, EList<ListChange> listChanges, Object value, int index)
{
CDOFeatureDelta delta = new CDOAddFeatureDeltaImpl(feature, index, value);
changes.add(delta);
oldList.add(index, value);
}
@Override
protected void createRemoveListChange(EList<?> oldList, EList<ListChange> listChanges, Object value, int index)
{
CDORemoveFeatureDeltaImpl delta = new CDORemoveFeatureDeltaImpl(feature, index);
// fix until ListDifferenceAnalyzer delivers the correct value (bug #308618).
delta.setValue(oldList.get(index));
changes.add(delta);
oldList.remove(index);
}
@Override
protected void createMoveListChange(EList<?> oldList, EList<ListChange> listChanges, Object value, int index, int toIndex)
{
CDOMoveFeatureDeltaImpl delta = new CDOMoveFeatureDeltaImpl(feature, toIndex, index);
// fix until ListDifferenceAnalyzer delivers the correct value (same problem as bug #308618).
delta.setValue(oldList.get(index));
changes.add(delta);
oldList.move(toIndex, index);
}
@Override
protected boolean equal(Object originValue, Object dirtyValue)
{
return compareValue(originValue, dirtyValue);
}
private void checkNoProxies(EList<?> list, CDORevision revision)
{
if (!((InternalCDORevision)revision).isUnchunked())
{
for (Object element : list)
{
if (element instanceof CDOElementProxy || element == CDOListImpl.UNINITIALIZED)
{
throw new PartialCollectionLoadingNotSupportedException("List contains proxy elements");
}
}
}
}
};
CDOList originList = originRevision.getList(feature);
CDOList dirtyList = dirtyRevision.getList(feature);
analyzer.analyzeLists(originList, dirtyList, new NOOPList());
if (!changes.isEmpty())
{
featureDeltas.put(feature, listFeatureDelta);
}
}
}
else
{
Object originValue = originData.get(feature, 0);
Object dirtyValue = dirtyData.get(feature, 0);
if (!compareValue(originValue, dirtyValue))
{
if (dirtyValue == null)
{
CDOFeatureDelta delta = new CDOUnsetFeatureDeltaImpl(feature);
addFeatureDelta(delta, null);
}
else
{
CDOFeatureDelta delta = new CDOSetFeatureDeltaImpl(feature, 0, dirtyValue, originValue);
addFeatureDelta(delta, null);
}
}
}
}
}
private boolean compareValue(Object originValue, Object dirtyValue)
{
Object origin = convertEObject(originValue);
Object dirty = convertEObject(dirtyValue);
if (origin == null)
{
return dirty == null;
}
if (dirty == null)
{
return false;
}
if (origin == dirty)
{
return true;
}
if (origin instanceof CDOID)
{
return false;
}
return origin.equals(dirty);
}
private Object convertEObject(Object value)
{
CDOID id = CDOIDUtil.getCDOID(value);
if (id != null)
{
return id;
}
return value;
}
@Override
public String toString()
{
return MessageFormat.format("CDORevisionDelta[{0}@{1}:{2}v{3} --> {4}]", eClass.getName(), id, branch.getID(), version, featureDeltas.values());
}
/**
* @author Eike Stepper
*/
public static class NOOPList implements EList<ListChange>
{
private static final EList<ListChange> LIST = ECollections.emptyEList();
public NOOPList()
{
}
public int size()
{
return 0;
}
public boolean isEmpty()
{
return true;
}
public boolean contains(Object o)
{
return false;
}
public Iterator<ListChange> iterator()
{
return LIST.iterator();
}
public Object[] toArray()
{
return LIST.toArray();
}
public <T> T[] toArray(T[] a)
{
return LIST.toArray(a);
}
public boolean add(ListChange o)
{
return false;
}
public boolean remove(Object o)
{
return false;
}
public boolean containsAll(Collection<?> c)
{
return false;
}
public boolean addAll(Collection<? extends ListChange> c)
{
return false;
}
public boolean addAll(int index, Collection<? extends ListChange> c)
{
return false;
}
public boolean removeAll(Collection<?> c)
{
return false;
}
public boolean retainAll(Collection<?> c)
{
return false;
}
public void clear()
{
}
public ListChange get(int index)
{
return LIST.get(index);
}
public ListChange set(int index, ListChange element)
{
return null;
}
public void add(int index, ListChange element)
{
}
public ListChange remove(int index)
{
return null;
}
public int indexOf(Object o)
{
return LIST.indexOf(o);
}
public int lastIndexOf(Object o)
{
return LIST.lastIndexOf(o);
}
public ListIterator<ListChange> listIterator()
{
return LIST.listIterator();
}
public ListIterator<ListChange> listIterator(int index)
{
return LIST.listIterator(index);
}
public List<ListChange> subList(int fromIndex, int toIndex)
{
return LIST.subList(fromIndex, toIndex);
}
public void move(int newPosition, ListChange object)
{
}
public ListChange move(int newPosition, int oldPosition)
{
return null;
}
}
}