/* | |
* Copyright (c) 2004 - 2012 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.id.CDOID; | |
import org.eclipse.emf.cdo.common.id.CDOWithID; | |
import org.eclipse.emf.cdo.common.model.CDOModelUtil; | |
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.CDORevisionDelta; | |
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.internal.common.revision.CDORevisionImpl; | |
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.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()) | |
{ | |
addFeatureDelta(((InternalCDOFeatureDelta)delta).copy()); | |
} | |
} | |
} | |
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(sourceRevision, targetRevision); | |
CDORevisionData originData = sourceRevision.data(); | |
CDORevisionData dirtyData = targetRevision.data(); | |
Object dirtyContainerID = dirtyData.getContainerID(); | |
if (dirtyContainerID instanceof CDOWithID) | |
{ | |
dirtyContainerID = ((CDOWithID)dirtyContainerID).cdoID(); | |
} | |
if (!compare(originData.getContainerID(), dirtyContainerID) | |
|| !compare(originData.getContainingFeatureID(), dirtyData.getContainingFeatureID()) | |
|| !compare(originData.getResourceID(), dirtyData.getResourceID())) | |
{ | |
addFeatureDelta(new CDOContainerFeatureDeltaImpl(dirtyData.getResourceID(), dirtyContainerID, | |
dirtyData.getContainingFeatureID())); | |
} | |
} | |
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 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()); | |
} | |
public void apply(CDORevision revision) | |
{ | |
for (CDOFeatureDelta featureDelta : featureDeltas.values()) | |
{ | |
((CDOFeatureDeltaImpl)featureDelta).apply(revision); | |
} | |
} | |
public void addFeatureDelta(CDOFeatureDelta delta) | |
{ | |
if (delta instanceof CDOListFeatureDelta) | |
{ | |
CDOListFeatureDelta deltas = (CDOListFeatureDelta)delta; | |
for (CDOFeatureDelta childDelta : deltas.getListChanges()) | |
{ | |
addFeatureDelta(childDelta); | |
} | |
} | |
else | |
{ | |
addSingleFeatureDelta(delta); | |
} | |
} | |
private void addSingleFeatureDelta(CDOFeatureDelta delta) | |
{ | |
EStructuralFeature feature = delta.getFeature(); | |
if (feature.isMany()) | |
{ | |
CDOListFeatureDeltaImpl listDelta = (CDOListFeatureDeltaImpl)featureDeltas.get(feature); | |
if (listDelta == null) | |
{ | |
listDelta = new CDOListFeatureDeltaImpl(feature); | |
featureDeltas.put(listDelta.getFeature(), listDelta); | |
} | |
// Remove all previous changes | |
if (delta instanceof CDOClearFeatureDelta || delta instanceof CDOUnsetFeatureDelta) | |
{ | |
listDelta.getListChanges().clear(); | |
} | |
listDelta.add(delta); | |
} | |
else | |
{ | |
featureDeltas.put(feature, delta); | |
} | |
} | |
public boolean adjustReferences(CDOReferenceAdjuster referenceAdjuster) | |
{ | |
boolean changed = false; | |
for (CDOFeatureDelta featureDelta : featureDeltas.values()) | |
{ | |
changed |= ((CDOFeatureDeltaImpl)featureDelta).adjustReferences(referenceAdjuster); | |
} | |
return changed; | |
} | |
public void accept(CDOFeatureDeltaVisitor visitor) | |
{ | |
for (CDOFeatureDelta featureDelta : featureDeltas.values()) | |
{ | |
((CDOFeatureDeltaImpl)featureDelta).accept(visitor); | |
} | |
} | |
private void compare(CDORevision originRevision, CDORevision dirtyRevision) | |
{ | |
CDORevisionData originData = originRevision.data(); | |
CDORevisionData dirtyData = dirtyRevision.data(); | |
for (final EStructuralFeature feature : CDOModelUtil.getAllPersistentFeatures(eClass)) | |
{ | |
if (feature.isMany()) | |
{ | |
if (originData.size(feature) > 0 && dirtyData.size(feature) == 0) | |
{ | |
addFeatureDelta(new CDOClearFeatureDeltaImpl(feature)); | |
} | |
else | |
{ | |
CDOListFeatureDelta listFeatureDelta = new CDOListFeatureDeltaImpl(feature); | |
final List<CDOFeatureDelta> changes = listFeatureDelta.getListChanges(); | |
ListDifferenceAnalyzer analyzer = new ListDifferenceAnalyzer() | |
{ | |
@Override | |
public void analyzeLists(EList<Object> oldList, EList<?> newList, EList<ListChange> listChanges) | |
{ | |
checkNoProxies(oldList); | |
checkNoProxies(newList); | |
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); | |
} | |
private void checkNoProxies(EList<?> list) | |
{ | |
for (Object element : list) | |
{ | |
if (element instanceof CDOElementProxy || element == CDOListImpl.UNINITIALIZED) | |
{ | |
throw new PartialCollectionLoadingNotSupportedException("List contains proxy elements"); | |
} | |
} | |
} | |
}; | |
CDOList originList = ((InternalCDORevision)originRevision).getList(feature); | |
CDOList dirtyList = ((InternalCDORevision)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 (!compare(originValue, dirtyValue)) | |
{ | |
if (dirtyValue == null) | |
{ | |
addFeatureDelta(new CDOUnsetFeatureDeltaImpl(feature)); | |
} | |
else | |
{ | |
addFeatureDelta(new CDOSetFeatureDeltaImpl(feature, 0, dirtyValue, originValue)); | |
} | |
} | |
} | |
} | |
} | |
private boolean compare(Object originValue, Object dirtyValue) | |
{ | |
return originValue == dirtyValue || originValue != null && dirtyValue != null && originValue.equals(dirtyValue); | |
} | |
@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; | |
} | |
} | |
} |