| /* |
| * Copyright (c) 2010-2013 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 |
| */ |
| package org.eclipse.emf.spi.cdo; |
| |
| import org.eclipse.emf.cdo.common.branch.CDOBranchVersion; |
| import org.eclipse.emf.cdo.common.commit.CDOChangeSet; |
| import org.eclipse.emf.cdo.common.commit.CDOChangeSetData; |
| import org.eclipse.emf.cdo.common.id.CDOID; |
| import org.eclipse.emf.cdo.common.id.CDOIDUtil; |
| import org.eclipse.emf.cdo.common.revision.CDOIDAndVersion; |
| import org.eclipse.emf.cdo.common.revision.CDORevision; |
| import org.eclipse.emf.cdo.common.revision.CDORevisionKey; |
| 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.CDOFeatureDelta.Type; |
| 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.CDORevisionDelta; |
| import org.eclipse.emf.cdo.common.revision.delta.CDOSetFeatureDelta; |
| import org.eclipse.emf.cdo.internal.common.commit.CDOChangeSetDataImpl; |
| import org.eclipse.emf.cdo.internal.common.revision.delta.CDOAddFeatureDeltaImpl; |
| import org.eclipse.emf.cdo.internal.common.revision.delta.CDOListFeatureDeltaImpl; |
| import org.eclipse.emf.cdo.internal.common.revision.delta.CDOMoveFeatureDeltaImpl; |
| import org.eclipse.emf.cdo.internal.common.revision.delta.CDORemoveFeatureDeltaImpl; |
| import org.eclipse.emf.cdo.internal.common.revision.delta.CDORevisionDeltaImpl; |
| import org.eclipse.emf.cdo.spi.common.revision.InternalCDOFeatureDelta; |
| import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionDelta; |
| import org.eclipse.emf.cdo.transaction.CDOMerger; |
| |
| import org.eclipse.net4j.util.CheckUtil; |
| import org.eclipse.net4j.util.collection.Pair; |
| |
| import org.eclipse.emf.common.util.BasicEList; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| |
| import java.text.MessageFormat; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| |
| /** |
| * If the meaning of this type isn't clear, there really should be more of a description here... |
| * |
| * @author Eike Stepper |
| * @since 3.0 |
| */ |
| public class DefaultCDOMerger implements CDOMerger |
| { |
| private final ResolutionPreference resolutionPreference; |
| |
| private CDOChangeSetData result; |
| |
| private Map<CDOID, Conflict> conflicts; |
| |
| private Map<CDOID, Object> targetMap; |
| |
| private Map<CDOID, Object> sourceMap; |
| |
| public DefaultCDOMerger() |
| { |
| this(ResolutionPreference.NONE); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public DefaultCDOMerger(ResolutionPreference resolutionPreference) |
| { |
| CheckUtil.checkArg(resolutionPreference, "resolutionPreference"); |
| this.resolutionPreference = resolutionPreference; |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public final ResolutionPreference getResolutionPreference() |
| { |
| return resolutionPreference; |
| } |
| |
| public CDOChangeSetData getResult() |
| { |
| return result; |
| } |
| |
| public Map<CDOID, Conflict> getConflicts() |
| { |
| return conflicts; |
| } |
| |
| public synchronized CDOChangeSetData merge(CDOChangeSet target, CDOChangeSet source) throws ConflictException |
| { |
| result = new CDOChangeSetDataImpl(); |
| conflicts = CDOIDUtil.createMap(); |
| |
| targetMap = createMap(target); |
| sourceMap = createMap(source); |
| |
| Set<CDOID> taken = new HashSet<CDOID>(); |
| for (Entry<CDOID, Object> entry : targetMap.entrySet()) |
| { |
| CDOID id = entry.getKey(); |
| Object targetData = entry.getValue(); |
| Object sourceData = sourceMap.get(id); |
| |
| if (merge(targetData, sourceData)) |
| { |
| taken.add(id); |
| } |
| } |
| |
| for (Entry<CDOID, Object> entry : sourceMap.entrySet()) |
| { |
| CDOID id = entry.getKey(); |
| if (taken.add(id)) |
| { |
| Object sourceData = entry.getValue(); |
| Object targetData = targetMap.get(id); |
| merge(targetData, sourceData); |
| } |
| } |
| |
| if (!conflicts.isEmpty()) |
| { |
| throw new ConflictException("Merger could not resolve all conflicts: " + conflicts, this, result); |
| } |
| |
| return result; |
| } |
| |
| protected boolean merge(Object targetData, Object sourceData) |
| { |
| Object data = null; |
| if (sourceData == null) |
| { |
| if (targetData instanceof CDORevision) |
| { |
| data = addedInTarget((CDORevision)targetData); |
| } |
| else if (targetData instanceof CDORevisionDelta) |
| { |
| data = changedInTarget((CDORevisionDelta)targetData); |
| } |
| else if (targetData instanceof CDOID) |
| { |
| data = detachedInTarget((CDOID)targetData); |
| } |
| } |
| else if (targetData == null) |
| { |
| if (sourceData instanceof CDORevision) |
| { |
| data = addedInSource((CDORevision)sourceData); |
| } |
| else if (sourceData instanceof CDORevisionDelta) |
| { |
| data = changedInSource((CDORevisionDelta)sourceData); |
| } |
| else if (sourceData instanceof CDOID) |
| { |
| data = detachedInSource((CDOID)sourceData); |
| } |
| } |
| else if (sourceData instanceof CDOID && targetData instanceof CDOID) |
| { |
| data = detachedInSourceAndTarget((CDOID)sourceData); |
| } |
| else if (sourceData instanceof CDORevisionDelta && targetData instanceof CDORevisionDelta) |
| { |
| data = changedInSourceAndTarget((CDORevisionDelta)targetData, (CDORevisionDelta)sourceData); |
| } |
| else if (sourceData instanceof CDORevision && targetData instanceof CDORevision) |
| { |
| data = addedInSourceAndTarget((CDORevision)targetData, (CDORevision)sourceData); |
| } |
| else if (sourceData instanceof CDORevisionDelta && targetData instanceof CDOID) |
| { |
| data = changedInSourceAndDetachedInTarget((CDORevisionDelta)sourceData); |
| } |
| else if (targetData instanceof CDORevisionDelta && sourceData instanceof CDOID) |
| { |
| data = changedInTargetAndDetachedInSource((CDORevisionDelta)targetData); |
| } |
| |
| return take(data); |
| } |
| |
| protected Object addedInTarget(CDORevision revision) |
| { |
| return revision; |
| } |
| |
| protected Object addedInSource(CDORevision revision) |
| { |
| return revision; |
| } |
| |
| protected Object addedInSourceAndTarget(CDORevision targetRevision, CDORevision sourceRevision) |
| { |
| return targetRevision; |
| } |
| |
| protected Object changedInTarget(CDORevisionDelta delta) |
| { |
| return delta; |
| } |
| |
| protected Object detachedInTarget(CDOID id) |
| { |
| return id; |
| } |
| |
| protected Object changedInSource(CDORevisionDelta delta) |
| { |
| return delta; |
| } |
| |
| protected Object detachedInSource(CDOID id) |
| { |
| return id; |
| } |
| |
| protected Object detachedInSourceAndTarget(CDOID id) |
| { |
| return id; |
| } |
| |
| protected Object changedInSourceAndTarget(CDORevisionDelta targetDelta, CDORevisionDelta sourceDelta) |
| { |
| switch (resolutionPreference) |
| { |
| case SOURCE_OVER_TARGET: |
| return sourceDelta; |
| |
| case TARGET_OVER_SOURCE: |
| return targetDelta; |
| |
| case NONE: |
| return new ChangedInSourceAndTargetConflict(targetDelta, sourceDelta); |
| |
| default: |
| throw new IllegalStateException("Illegal resolution preference: " + resolutionPreference); |
| } |
| } |
| |
| protected Object changedInSourceAndDetachedInTarget(CDORevisionDelta sourceDelta) |
| { |
| switch (resolutionPreference) |
| { |
| case SOURCE_OVER_TARGET: |
| return sourceDelta; // TODO Do we need to "recreate" the source revision as NEW? |
| |
| case TARGET_OVER_SOURCE: |
| return sourceDelta.getID(); // Indicate detachment |
| |
| case NONE: |
| return new ChangedInSourceAndDetachedInTargetConflict(sourceDelta); |
| |
| default: |
| throw new IllegalStateException("Illegal resolution preference: " + resolutionPreference); |
| } |
| } |
| |
| protected Object changedInTargetAndDetachedInSource(CDORevisionDelta targetDelta) |
| { |
| switch (resolutionPreference) |
| { |
| case SOURCE_OVER_TARGET: |
| return targetDelta.getID(); |
| |
| case TARGET_OVER_SOURCE: |
| return targetDelta; // TODO Do we need to "recreate" the target revision as NEW? |
| |
| case NONE: |
| return new ChangedInTargetAndDetachedInSourceConflict(targetDelta); |
| |
| default: |
| throw new IllegalStateException("Illegal resolution preference: " + resolutionPreference); |
| } |
| } |
| |
| protected Map<CDOID, Object> getTargetMap() |
| { |
| return targetMap; |
| } |
| |
| protected Map<CDOID, Object> getSourceMap() |
| { |
| return sourceMap; |
| } |
| |
| private Map<CDOID, Object> createMap(CDOChangeSetData changeSetData) |
| { |
| Map<CDOID, Object> map = CDOIDUtil.createMap(); |
| for (CDOIDAndVersion data : changeSetData.getNewObjects()) |
| { |
| map.put(data.getID(), data); |
| } |
| |
| for (CDORevisionKey data : changeSetData.getChangedObjects()) |
| { |
| map.put(data.getID(), data); |
| } |
| |
| for (CDOIDAndVersion data : changeSetData.getDetachedObjects()) |
| { |
| map.put(data.getID(), data.getID()); |
| } |
| |
| return map; |
| } |
| |
| private boolean take(Object data) |
| { |
| if (data instanceof Pair<?, ?>) |
| { |
| Pair<?, ?> pair = (Pair<?, ?>)data; |
| boolean taken = takeNoPair(pair.getElement1()); |
| taken |= takeNoPair(pair.getElement2()); |
| return taken; |
| } |
| |
| return takeNoPair(data); |
| } |
| |
| private boolean takeNoPair(Object data) |
| { |
| if (data instanceof CDORevision) |
| { |
| result.getNewObjects().add((CDORevision)data); |
| } |
| else if (data instanceof CDORevisionDelta) |
| { |
| result.getChangedObjects().add((CDORevisionDelta)data); |
| } |
| else if (data instanceof CDOID) |
| { |
| result.getDetachedObjects().add(CDOIDUtil.createIDAndVersion((CDOID)data, CDOBranchVersion.UNSPECIFIED_VERSION)); |
| } |
| else if (data instanceof Conflict) |
| { |
| Conflict conflict = (Conflict)data; |
| conflicts.put(conflict.getID(), conflict); |
| } |
| else if (data != null) |
| { |
| throw new IllegalArgumentException("Must be a CDORevision, a CDORevisionDelta, a CDOID, a Conflict or null: " |
| + data); |
| } |
| else |
| { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Enumerates the possible resolution preferences that can be used with a {@link DefaultCDOMerger}. |
| * |
| * @since 4.2 |
| * @author Eike Stepper |
| */ |
| public static enum ResolutionPreference |
| { |
| NONE, |
| |
| @Deprecated |
| SOURCE_OVER_TARGET, |
| |
| @Deprecated |
| TARGET_OVER_SOURCE, |
| |
| @Deprecated |
| DETACH_OVER_CHANGE, |
| |
| @Deprecated |
| CHANGE_OVER_DETACH |
| } |
| |
| /** |
| * If the meaning of this type isn't clear, there really should be more of a description here... |
| * |
| * @author Eike Stepper |
| */ |
| public static abstract class Conflict |
| { |
| public Conflict() |
| { |
| } |
| |
| public abstract CDOID getID(); |
| } |
| |
| /** |
| * If the meaning of this type isn't clear, there really should be more of a description here... |
| * |
| * @author Eike Stepper |
| */ |
| public static class ChangedInSourceAndTargetConflict extends Conflict |
| { |
| private CDORevisionDelta targetDelta; |
| |
| private CDORevisionDelta sourceDelta; |
| |
| public ChangedInSourceAndTargetConflict(CDORevisionDelta targetDelta, CDORevisionDelta sourceDelta) |
| { |
| this.targetDelta = targetDelta; |
| this.sourceDelta = sourceDelta; |
| } |
| |
| @Override |
| public CDOID getID() |
| { |
| return targetDelta.getID(); |
| } |
| |
| public CDORevisionDelta getTargetDelta() |
| { |
| return targetDelta; |
| } |
| |
| public CDORevisionDelta getSourceDelta() |
| { |
| return sourceDelta; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return MessageFormat.format("ChangedInSourceAndTarget[target={0}, source={1}]", targetDelta, sourceDelta); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * If the meaning of this type isn't clear, there really should be more of a description here... |
| * |
| * @author Eike Stepper |
| */ |
| public static class ChangedInSourceAndDetachedInTargetConflict extends Conflict |
| { |
| private CDORevisionDelta sourceDelta; |
| |
| public ChangedInSourceAndDetachedInTargetConflict(CDORevisionDelta sourceDelta) |
| { |
| this.sourceDelta = sourceDelta; |
| } |
| |
| @Override |
| public CDOID getID() |
| { |
| return sourceDelta.getID(); |
| } |
| |
| public CDORevisionDelta getSourceDelta() |
| { |
| return sourceDelta; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return MessageFormat.format("ChangedInSourceAndDetachedInTarget[source={1}]", sourceDelta); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * If the meaning of this type isn't clear, there really should be more of a description here... |
| * |
| * @author Eike Stepper |
| */ |
| public static class ChangedInTargetAndDetachedInSourceConflict extends Conflict |
| { |
| private CDORevisionDelta targetDelta; |
| |
| public ChangedInTargetAndDetachedInSourceConflict(CDORevisionDelta targetDelta) |
| { |
| this.targetDelta = targetDelta; |
| } |
| |
| @Override |
| public CDOID getID() |
| { |
| return targetDelta.getID(); |
| } |
| |
| public CDORevisionDelta getTargetDelta() |
| { |
| return targetDelta; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return MessageFormat.format("ChangedInTargetAndDetachedInSource[target={0}]", targetDelta); //$NON-NLS-1$ |
| } |
| } |
| |
| /** |
| * If the meaning of this type isn't clear, there really should be more of a description here... |
| * |
| * @author Eike Stepper |
| */ |
| public static class PerFeature extends DefaultCDOMerger |
| { |
| public PerFeature() |
| { |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public PerFeature(ResolutionPreference resolutionPreference) |
| { |
| super(resolutionPreference); |
| } |
| |
| @Override |
| protected Object changedInSourceAndTarget(CDORevisionDelta targetDelta, CDORevisionDelta sourceDelta) |
| { |
| InternalCDORevisionDelta result = new CDORevisionDeltaImpl(targetDelta, false); |
| ChangedInSourceAndTargetConflict conflict = null; |
| |
| Map<EStructuralFeature, CDOFeatureDelta> targetMap = ((InternalCDORevisionDelta)targetDelta).getFeatureDeltaMap(); |
| Map<EStructuralFeature, CDOFeatureDelta> sourceMap = ((InternalCDORevisionDelta)sourceDelta).getFeatureDeltaMap(); |
| |
| for (CDOFeatureDelta targetFeatureDelta : targetMap.values()) |
| { |
| EStructuralFeature feature = targetFeatureDelta.getFeature(); |
| CDOFeatureDelta sourceFeatureDelta = sourceMap.get(feature); |
| |
| if (sourceFeatureDelta == null) |
| { |
| CDOFeatureDelta featureDelta = changedInTarget(targetFeatureDelta); |
| if (featureDelta != null) |
| { |
| result.addFeatureDelta(featureDelta, null); |
| } |
| } |
| else |
| { |
| CDOFeatureDelta featureDelta = changedInSourceAndTarget(targetFeatureDelta, sourceFeatureDelta); |
| if (featureDelta != null) |
| { |
| result.addFeatureDelta(featureDelta, null); |
| } |
| else |
| { |
| if (conflict == null) |
| { |
| ResolutionPreference resolutionPreference = getResolutionPreference(); |
| switch (resolutionPreference) |
| { |
| case SOURCE_OVER_TARGET: |
| // TODO: implement DefaultCDOMerger.PerFeature.changedInSourceAndTarget(targetDelta, sourceDelta) |
| throw new UnsupportedOperationException(); |
| |
| case TARGET_OVER_SOURCE: |
| // TODO: implement DefaultCDOMerger.PerFeature.changedInSourceAndTarget(targetDelta, sourceDelta) |
| throw new UnsupportedOperationException(); |
| |
| case NONE: |
| conflict = new ChangedInSourceAndTargetConflict(new CDORevisionDeltaImpl(targetDelta, false), |
| new CDORevisionDeltaImpl(sourceDelta, false)); |
| break; |
| |
| default: |
| throw new IllegalStateException("Illegal resolution preference: " + resolutionPreference); |
| } |
| } |
| |
| ((InternalCDORevisionDelta)conflict.getTargetDelta()).addFeatureDelta(targetFeatureDelta, null); |
| ((InternalCDORevisionDelta)conflict.getSourceDelta()).addFeatureDelta(sourceFeatureDelta, null); |
| } |
| } |
| } |
| |
| for (CDOFeatureDelta sourceFeatureDelta : sourceMap.values()) |
| { |
| EStructuralFeature feature = sourceFeatureDelta.getFeature(); |
| CDOFeatureDelta targetFeatureDelta = targetMap.get(feature); |
| |
| if (targetFeatureDelta == null) |
| { |
| CDOFeatureDelta featureDelta = changedInSource(sourceFeatureDelta); |
| if (featureDelta != null) |
| { |
| result.addFeatureDelta(featureDelta, null); |
| } |
| } |
| } |
| |
| if (result.isEmpty()) |
| { |
| return conflict; |
| } |
| |
| if (conflict != null) |
| { |
| return Pair.create(result, conflict); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * @return the result feature delta, or <code>null</code> to ignore the change. |
| */ |
| protected CDOFeatureDelta changedInTarget(CDOFeatureDelta featureDelta) |
| { |
| return featureDelta; |
| } |
| |
| /** |
| * @return the result feature delta, or <code>null</code> to ignore the change. |
| */ |
| protected CDOFeatureDelta changedInSource(CDOFeatureDelta featureDelta) |
| { |
| return featureDelta; |
| } |
| |
| /** |
| * @return the result feature delta, or <code>null</code> to indicate an unresolved conflict. |
| */ |
| protected CDOFeatureDelta changedInSourceAndTarget(CDOFeatureDelta targetFeatureDelta, |
| CDOFeatureDelta sourceFeatureDelta) |
| { |
| EStructuralFeature feature = targetFeatureDelta.getFeature(); |
| if (feature.isMany()) |
| { |
| return changedInSourceAndTargetManyValued(feature, targetFeatureDelta, sourceFeatureDelta); |
| } |
| |
| return changedInSourceAndTargetSingleValued(feature, targetFeatureDelta, sourceFeatureDelta); |
| } |
| |
| /** |
| * @return the result feature delta, or <code>null</code> to indicate an unresolved conflict. |
| */ |
| protected CDOFeatureDelta changedInSourceAndTargetManyValued(EStructuralFeature feature, |
| CDOFeatureDelta targetFeatureDelta, CDOFeatureDelta sourceFeatureDelta) |
| { |
| return null; |
| } |
| |
| /** |
| * @return the result feature delta, or <code>null</code> to indicate an unresolved conflict. |
| */ |
| protected CDOFeatureDelta changedInSourceAndTargetSingleValued(EStructuralFeature feature, |
| CDOFeatureDelta targetFeatureDelta, CDOFeatureDelta sourceFeatureDelta) |
| { |
| if (targetFeatureDelta.isStructurallyEqual(sourceFeatureDelta)) |
| { |
| return targetFeatureDelta; |
| } |
| |
| return null; |
| } |
| |
| /** |
| * If the meaning of this type isn't clear, there really should be more of a description here... |
| * |
| * @author Eike Stepper |
| */ |
| public static class ManyValued extends PerFeature |
| { |
| public ManyValued() |
| { |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| public ManyValued(ResolutionPreference resolutionPreference) |
| { |
| super(resolutionPreference); |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected boolean treatAsUnique(EStructuralFeature feature) |
| { |
| return feature.isUnique(); |
| } |
| |
| @Override |
| protected CDOFeatureDelta changedInSourceAndTargetManyValued(EStructuralFeature feature, |
| CDOFeatureDelta targetFeatureDelta, CDOFeatureDelta sourceFeatureDelta) |
| { |
| if (targetFeatureDelta instanceof CDOListFeatureDelta && sourceFeatureDelta instanceof CDOListFeatureDelta) |
| { |
| // Initialize work lists with virtual elements |
| int originSize = ((CDOListFeatureDelta)sourceFeatureDelta.copy()).getOriginSize(); |
| BasicEList<Element> ancestorList = new BasicEList<Element>(originSize); |
| PerSide<BasicEList<Element>> listPerSide = new PerSide<BasicEList<Element>>(); |
| |
| initWorkLists(originSize, ancestorList, listPerSide); |
| |
| // Apply list changes to source and target work lists |
| PerSide<List<CDOFeatureDelta>> changesPerSide = new PerSide<List<CDOFeatureDelta>>( |
| copyListChanges(sourceFeatureDelta), copyListChanges(targetFeatureDelta)); |
| Map<Object, List<Element>> additions = new HashMap<Object, List<Element>>(); |
| Map<CDOFeatureDelta, Element> allElements = new HashMap<CDOFeatureDelta, Element>(); |
| |
| applyChangesToWorkList(Side.SOURCE, listPerSide, changesPerSide, allElements, additions); |
| applyChangesToWorkList(Side.TARGET, listPerSide, changesPerSide, allElements, additions); |
| |
| // Pick changes from source and target sides into the merge result |
| CDOListFeatureDelta result = new CDOListFeatureDeltaImpl(feature, originSize); |
| List<CDOFeatureDelta> resultChanges = result.getListChanges(); |
| |
| pickChangesIntoResult(Side.SOURCE, feature, ancestorList, changesPerSide, allElements, additions, |
| resultChanges); |
| pickChangesIntoResult(Side.TARGET, feature, ancestorList, changesPerSide, allElements, additions, |
| resultChanges); |
| |
| return result; |
| } |
| |
| return super.changedInSourceAndTargetManyValued(feature, targetFeatureDelta, sourceFeatureDelta); |
| } |
| |
| private void initWorkLists(int originSize, BasicEList<Element> ancestorList, |
| PerSide<BasicEList<Element>> listPerSide) |
| { |
| BasicEList<Element> sourceList = new BasicEList<Element>(originSize); |
| BasicEList<Element> targetList = new BasicEList<Element>(originSize); |
| |
| for (int i = 0; i < originSize; i++) |
| { |
| Element element = new Element(i); |
| ancestorList.add(element); |
| sourceList.add(element); |
| targetList.add(element); |
| } |
| |
| listPerSide.set(Side.SOURCE, sourceList); |
| listPerSide.set(Side.TARGET, targetList); |
| } |
| |
| private List<CDOFeatureDelta> copyListChanges(CDOFeatureDelta featureDelta) |
| { |
| CDOListFeatureDelta listFeatureDelta = (CDOListFeatureDelta)featureDelta.copy(); |
| List<CDOFeatureDelta> copy = listFeatureDelta.getListChanges(); |
| |
| if (!copy.isEmpty()) |
| { |
| CDOFeatureDelta.Type firstType = copy.get(0).getType(); |
| if (firstType == Type.CLEAR || firstType == Type.UNSET) |
| { |
| copy.remove(0); |
| |
| List<CDOFeatureDelta> expandedDeltas = expandClearDelta(listFeatureDelta); |
| copy.addAll(0, expandedDeltas); |
| } |
| } |
| |
| return copy; |
| } |
| |
| private List<CDOFeatureDelta> expandClearDelta(CDOListFeatureDelta listFeatureDelta) |
| { |
| EStructuralFeature feature = listFeatureDelta.getFeature(); |
| int originSize = listFeatureDelta.getOriginSize(); |
| List<CDOFeatureDelta> expandedDeltas = new ArrayList<CDOFeatureDelta>(originSize); |
| |
| for (int i = 0; i < originSize; i++) |
| { |
| expandedDeltas.add(new CDORemoveFeatureDeltaImpl(feature, 0)); |
| } |
| |
| return expandedDeltas; |
| } |
| |
| private void applyChangesToWorkList(Side side, PerSide<BasicEList<Element>> listPerSide, |
| PerSide<List<CDOFeatureDelta>> changesPerSide, Map<CDOFeatureDelta, Element> allElements, |
| Map<Object, List<Element>> additions) |
| { |
| BasicEList<Element> list = listPerSide.get(side); |
| List<CDOFeatureDelta> changes = changesPerSide.get(side); |
| for (CDOFeatureDelta change : changes) |
| { |
| Type changeType = change.getType(); |
| switch (changeType) |
| { |
| case ADD: |
| { |
| CDOAddFeatureDelta addChange = (CDOAddFeatureDelta)change; |
| |
| Element element = new Element(-1); |
| element.set(side, addChange); |
| allElements.put(addChange, element); |
| |
| list.add(addChange.getIndex(), element); |
| rememberAddition(addChange.getValue(), element, additions); |
| break; |
| } |
| |
| case REMOVE: |
| { |
| CDORemoveFeatureDelta removeChange = (CDORemoveFeatureDelta)change; |
| |
| Element element = list.remove(removeChange.getIndex()); |
| element.set(side, removeChange); |
| allElements.put(removeChange, element); |
| break; |
| } |
| |
| case SET: |
| { |
| CDOSetFeatureDelta setChange = (CDOSetFeatureDelta)change; |
| |
| Element newElement = new Element(-1); |
| newElement.set(side, setChange); |
| rememberAddition(setChange.getValue(), newElement, additions); |
| |
| Element oldElement = list.set(setChange.getIndex(), newElement); |
| oldElement.set(side, setChange); |
| allElements.put(setChange, oldElement); |
| break; |
| } |
| |
| case MOVE: |
| { |
| CDOMoveFeatureDelta moveChange = (CDOMoveFeatureDelta)change; |
| |
| Element element = list.move(moveChange.getNewPosition(), moveChange.getOldPosition()); |
| element.set(side, moveChange); |
| allElements.put(moveChange, element); |
| break; |
| } |
| |
| case CLEAR: |
| case UNSET: |
| // These deltas should have been replaced by multiple REMOVE deltas in copyListChanges() |
| throw new IllegalStateException("Unhandled change type: " + changeType); |
| |
| default: |
| throw new IllegalStateException("Illegal change type: " + changeType); |
| } |
| } |
| } |
| |
| private void rememberAddition(Object value, Element element, Map<Object, List<Element>> additions) |
| { |
| List<Element> additionsList = additions.get(value); |
| if (additionsList == null) |
| { |
| additionsList = new ArrayList<Element>(1); |
| additions.put(value, additionsList); |
| } |
| |
| additionsList.add(element); |
| } |
| |
| private void pickChangesIntoResult(Side side, EStructuralFeature feature, BasicEList<Element> ancestorList, |
| PerSide<List<CDOFeatureDelta>> changesPerSide, Map<CDOFeatureDelta, Element> allElements, |
| Map<Object, List<Element>> additions, List<CDOFeatureDelta> result) |
| { |
| List<CDOFeatureDelta> changes = changesPerSide.get(side); |
| for (CDOFeatureDelta change : changes) |
| { |
| Type changeType = change.getType(); |
| switch (changeType) |
| { |
| case ADD: |
| { |
| CDOAddFeatureDeltaImpl addChange = (CDOAddFeatureDeltaImpl)change; |
| result.add(addChange); |
| |
| int sideIndex = addChange.getIndex(); |
| int ancestorIndex = sideIndex; |
| |
| int ancestorEnd = ancestorList.size(); |
| if (ancestorIndex > ancestorEnd) |
| { |
| // TODO Better way to adjust ancestor indexes? |
| ancestorIndex = ancestorEnd; |
| addChange.setIndex(ancestorIndex); |
| } |
| |
| Element newElement = allElements.get(addChange); |
| ancestorList.add(ancestorIndex, newElement); |
| |
| if (treatAsUnique(feature)) |
| { |
| // Detect and remove corresponding AddDeltas from the other side |
| Object value = addChange.getValue(); |
| List<Element> elementsToAdd = additions.get(value); |
| if (elementsToAdd != null) |
| { |
| for (Element element : elementsToAdd) |
| { |
| CDOAddFeatureDelta otherAdd = (CDOAddFeatureDelta)element.get(other(side)); |
| if (otherAdd != null) |
| { |
| element.set(other(side), null); |
| |
| // Not taking an AddDelta has the same effect on indexes as a removal of the element |
| List<CDOFeatureDelta> otherChanges = changesPerSide.get(other(side)); |
| int otherIndex = otherAdd.getIndex(); |
| adjustAfterRemoval(otherChanges, otherIndex, addChange); |
| } |
| } |
| } |
| } |
| |
| break; |
| } |
| |
| case REMOVE: |
| { |
| CDORemoveFeatureDeltaImpl removeChange = (CDORemoveFeatureDeltaImpl)change; |
| result.add(removeChange); |
| |
| Element removedElement = allElements.get(removeChange); |
| int ancestorIndex = ancestorList.indexOf(removedElement); |
| removeChange.setIndex(ancestorIndex); |
| ancestorList.remove(ancestorIndex); |
| |
| // Detect and remove a potential duplicate RemoveDelta from the other side |
| CDOFeatureDelta otherChange = removedElement.get(other(side)); |
| if (otherChange != null) |
| { |
| Type otherChangeType = otherChange.getType(); |
| switch (otherChangeType) |
| { |
| case REMOVE: |
| { |
| CDORemoveFeatureDelta otherRemove = (CDORemoveFeatureDelta)otherChange; |
| removedElement.set(other(side), null); |
| |
| // Not taking a RemoveDelta has the same effect on indexes as an addition of the element |
| List<CDOFeatureDelta> otherChanges = changesPerSide.get(other(side)); |
| int otherIndex = otherRemove.getIndex(); |
| adjustAfterAddition(otherChanges, otherIndex, otherRemove); |
| break; |
| } |
| |
| case MOVE: |
| { |
| CDOMoveFeatureDelta otherMove = (CDOMoveFeatureDelta)otherChange; |
| removedElement.set(other(side), null); |
| |
| // Not taking a MoveDelta has the same effect on indexes as a reverse move of the element |
| List<CDOFeatureDelta> otherChanges = changesPerSide.get(other(side)); |
| int otherOldPosition = otherMove.getOldPosition(); |
| int otherNewPosition = otherMove.getNewPosition(); |
| adjustAfterMove(otherChanges, otherOldPosition, otherNewPosition, otherMove); |
| break; |
| } |
| |
| default: |
| throw new IllegalStateException("Unexpected change type: " + otherChangeType); |
| } |
| } |
| |
| break; |
| } |
| |
| case SET: |
| { |
| throw new IllegalStateException("Unhandled change type: " + changeType); |
| // CDOSetFeatureDelta setChange = (CDOSetFeatureDelta)change; |
| // break; |
| } |
| |
| case MOVE: |
| { |
| CDOMoveFeatureDeltaImpl moveChange = (CDOMoveFeatureDeltaImpl)change; |
| int sideOldPosition = moveChange.getOldPosition(); |
| int sideNewPosition = moveChange.getNewPosition(); |
| |
| Element movedElement = allElements.get(moveChange); |
| CDOFeatureDelta otherChange = movedElement.get(other(side)); |
| |
| if (otherChange != null) |
| { |
| Type otherChangeType = otherChange.getType(); |
| switch (otherChangeType) |
| { |
| case REMOVE: |
| { |
| // Prioritize the RemoveDelta of the other side, delete the MoveDelta from this side |
| adjustAfterMove(changes, sideOldPosition, sideNewPosition, moveChange); |
| movedElement.set(side, null); |
| return; |
| } |
| |
| case MOVE: |
| { |
| CDOMoveFeatureDelta otherMove = (CDOMoveFeatureDelta)otherChange; |
| movedElement.set(other(side), null); |
| |
| // Not taking a MoveDelta has the same effect on indexes as a reverse move of the element |
| List<CDOFeatureDelta> otherChanges = changesPerSide.get(other(side)); |
| int otherOldPosition = otherMove.getOldPosition(); |
| int otherNewPosition = otherMove.getNewPosition(); |
| adjustAfterMove(otherChanges, otherOldPosition, otherNewPosition, otherMove); |
| movedElement.set(other(side), null); |
| break; |
| } |
| |
| default: |
| throw new IllegalStateException("Unexpected change type: " + otherChangeType); |
| } |
| } |
| |
| int positionDelta = sideNewPosition - sideOldPosition; |
| int ancestorOldPosition = ancestorList.indexOf(movedElement); |
| int ancestorNewPosition = ancestorOldPosition + positionDelta; |
| if (ancestorNewPosition < 0) |
| { |
| ancestorNewPosition = 0; |
| } |
| |
| int ancestorEnd = ancestorList.size() - 1; |
| if (ancestorNewPosition > ancestorEnd) |
| { |
| ancestorNewPosition = ancestorEnd; |
| } |
| |
| moveChange.setOldPosition(ancestorOldPosition); |
| moveChange.setNewPosition(ancestorNewPosition); |
| result.add(moveChange); |
| |
| ancestorList.move(ancestorNewPosition, ancestorOldPosition); |
| break; |
| } |
| |
| case CLEAR: |
| case UNSET: |
| |
| default: |
| throw new IllegalStateException("Illegal change type: " + changeType); |
| } |
| } |
| } |
| |
| private static void adjustAfterAddition(List<CDOFeatureDelta> list, int index, CDOFeatureDelta deltaToRemove) |
| { |
| for (Iterator<CDOFeatureDelta> it = list.iterator(); it.hasNext();) |
| { |
| CDOFeatureDelta delta = it.next(); |
| if (delta == deltaToRemove) |
| { |
| it.remove(); |
| continue; |
| } |
| |
| if (delta instanceof InternalCDOFeatureDelta.WithIndex) |
| { |
| InternalCDOFeatureDelta.WithIndex withIndex = (InternalCDOFeatureDelta.WithIndex)delta; |
| withIndex.adjustAfterAddition(index); |
| } |
| } |
| } |
| |
| private static void adjustAfterRemoval(List<CDOFeatureDelta> list, int index, CDOFeatureDelta deltaToRemove) |
| { |
| for (Iterator<CDOFeatureDelta> it = list.iterator(); it.hasNext();) |
| { |
| CDOFeatureDelta delta = it.next(); |
| if (delta == deltaToRemove) |
| { |
| it.remove(); |
| continue; |
| } |
| |
| if (delta instanceof InternalCDOFeatureDelta.WithIndex) |
| { |
| InternalCDOFeatureDelta.WithIndex withIndex = (InternalCDOFeatureDelta.WithIndex)delta; |
| withIndex.adjustAfterRemoval(index); |
| } |
| } |
| } |
| |
| private static void adjustAfterMove(List<CDOFeatureDelta> list, int oldPosition, int newPosition, |
| CDOFeatureDelta deltaToRemove) |
| { |
| for (Iterator<CDOFeatureDelta> it = list.iterator(); it.hasNext();) |
| { |
| CDOFeatureDelta delta = it.next(); |
| if (delta == deltaToRemove) |
| { |
| it.remove(); |
| continue; |
| } |
| |
| if (delta instanceof InternalCDOFeatureDelta.WithIndex) |
| { |
| InternalCDOFeatureDelta.WithIndex withIndex = (InternalCDOFeatureDelta.WithIndex)delta; |
| withIndex.adjustAfterRemoval(oldPosition); |
| withIndex.adjustAfterAddition(newPosition); |
| } |
| } |
| } |
| |
| /** |
| * @since 4.2 |
| */ |
| protected static Side other(Side side) |
| { |
| if (side == Side.SOURCE) |
| { |
| return Side.TARGET; |
| } |
| |
| return Side.SOURCE; |
| } |
| |
| /** |
| * Enumerates the possible sides of a merge, i.e., {@link #SOURCE} and {@link #TARGET}. |
| * |
| * @author Eike Stepper |
| * @since 4.2 |
| */ |
| public static enum Side |
| { |
| SOURCE, TARGET |
| } |
| |
| /** |
| * Holds data for the source and target sides. |
| * |
| * @author Eike Stepper |
| * @since 4.2 |
| */ |
| public static class PerSide<T> |
| { |
| private T source; |
| |
| private T target; |
| |
| public PerSide() |
| { |
| } |
| |
| public PerSide(T source, T target) |
| { |
| this.source = source; |
| this.target = target; |
| } |
| |
| public final T get(Side side) |
| { |
| if (side == Side.SOURCE) |
| { |
| return source; |
| } |
| |
| return target; |
| } |
| |
| public final void set(Side side, T value) |
| { |
| if (side == Side.SOURCE) |
| { |
| this.source = value; |
| } |
| else |
| { |
| this.target = value; |
| } |
| } |
| |
| @Override |
| public String toString() |
| { |
| return "source: " + source + "\ntarget: " + target; |
| } |
| } |
| |
| /** |
| * A virtual list element to establish unique relations between ancestor, source and target sides. |
| * |
| * @author Eike Stepper |
| * @since 4.2 |
| */ |
| public static final class Element extends PerSide<CDOFeatureDelta> |
| { |
| private final int ancestorIndex; |
| |
| public Element(int ancestorIndex) |
| { |
| this.ancestorIndex = ancestorIndex; |
| } |
| |
| public int getAncestorIndex() |
| { |
| return ancestorIndex; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return String.valueOf(ancestorIndex); |
| } |
| } |
| |
| @Deprecated |
| protected CDOListFeatureDelta createResult(EStructuralFeature feature) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Deprecated |
| protected void handleListDelta(List<CDOFeatureDelta> resultList, List<CDOFeatureDelta> listToHandle, |
| List<CDOFeatureDelta> listToAdjust) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Deprecated |
| protected boolean handleListDeltaAdd(List<CDOFeatureDelta> resultList, CDOAddFeatureDelta addDelta, |
| List<CDOFeatureDelta> listToAdjust) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Deprecated |
| protected boolean handleListDeltaRemove(List<CDOFeatureDelta> resultList, CDORemoveFeatureDelta removeDelta, |
| List<CDOFeatureDelta> listToAdjust) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Deprecated |
| protected boolean handleListDeltaMove(List<CDOFeatureDelta> resultList, CDOMoveFeatureDelta moveDelta, |
| List<CDOFeatureDelta> listToAdjust) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Deprecated |
| public static void adjustAfterAddition(List<CDOFeatureDelta> list, int index) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Deprecated |
| public static void adjustAfterRemoval(List<CDOFeatureDelta> list, int index) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| |
| @Deprecated |
| public static void adjustAfterMove(List<CDOFeatureDelta> list, int oldPosition, int newPosition) |
| { |
| throw new UnsupportedOperationException(); |
| } |
| } |
| } |
| } |