/**
* Copyright (c) 2007-2011 IBM Corporation 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:
* IBM - Initial API and implementation
*/
package org.eclipse.emf.ecore.change.util;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.change.ChangeFactory;
import org.eclipse.emf.ecore.change.ChangeKind;
import org.eclipse.emf.ecore.change.ListChange;
/**
* Abstract class implementing the methods required to compute differences between
* lists. The differences are described by {@link ListChange} objects.
* @since 2.3
*/
public class ListDifferenceAnalyzer
{
/**
* Analyzes the differences between two lists, returning the {@link ListChange list changes}
* that describe how the <code>newList</code> could be changed to the contents of
* <code>oldList</code>. The lists are not modified by this method.</p>
* @param oldList
* @param newList
* @return a list of {@link ListChange}
*/
public EList<ListChange> analyzeLists(EList<?> oldList, EList<?> newList)
{
EList<ListChange> listChanges = new BasicEList<ListChange>();
analyzeLists(new BasicEList<Object>(oldList), newList, listChanges);
return listChanges;
}
/**
* <p>Analyzes the differences between two lists, adding new {@link ListChange list changes} to the
* specified <code>listChanges</code>. The list changes describe how the <code>newList</code>
* should be manipulated in order to have the same contents of <code>oldList</code></p>
*
* <p>This methods changes the contents of <code>oldList</code></p>
*
* @param oldList
* @param newList
* @param listChanges
*/
public void analyzeLists(EList<Object> oldList, EList<?> newList, EList<ListChange> listChanges)
{
createListChanges(new BasicEList<Object>(oldList), newList, listChanges);
}
protected void createListChanges(EList<Object> oldList, EList<?> newList, EList<ListChange> listChanges)
{
// Keep track of the list sizes.
//
int oldListSize = oldList.size();
int newListSize = newList.size();
// Track which value at an index in the old list matches the value at each index in the new list.
// A zero indicates an unmatched item while an index is offset by one to avoid using the zero index.
//
int[] newListSources = new int [newListSize];
// Iterate over the old list.
// We'll remove unmatched items as we proceed.
// Also keep track of which entry in the sources at which to start,
// so we can skip over all the already matched values at the start of the list.
//
for (int i = 0, start = 0; i < oldListSize; )
{
// Get the value at that the index.
//
Object oldValue = oldList.get(i);
// Mark it as one that needs to be removed until we find a match.
//
boolean remove = true;
// Keep track when all slots in the new list have been consumed.
//
boolean allSlotsMatched = true;
// Look for a match for the old value in the new list.
//
LOOP:
for (int j = start; j < newListSize; ++j)
{
// If the tracked entry is uninitialized...
//
int source = newListSources[j];
if (source == 0)
{
// Get the new value at the index and compare it to the old value.
//
Object newValue = newList.get(j);
if (equal(oldValue, newValue))
{
// If they're equal, indicate that the new value at the index j matches the old value at index i.
//
newListSources[j] = i + 1;
// If this index was the start, increment the start.
//
if (start == j)
{
++start;
}
// The value is matched so don't remove it when exiting the loop.
//
remove = false;
break LOOP;
}
// If all slots might be matched, but we just hit one that wasn't...
//
else if (allSlotsMatched)
{
// Make that the starting slot and make sure no subsequent slot is marked as the starting slot.
//
start = j;
allSlotsMatched = false;
}
}
}
// If we're done the loop without finding a match...
//
if (remove)
{
// Remove the old value thereby reducing the size of the list.
//
createRemoveListChange(oldList, listChanges, oldValue, i);
--oldListSize;
}
else
{
// Proceed with the next old value.
//
++i;
}
}
// Create an array where each index represents the target index at which the value at the index in the old list should end up.
//
int[] oldListTargets = new int [oldListSize];
// Keep a count of the number of values that need to be added, because we won't add anything until the rest of the list is in the right order.
// That way we ensure that we add at exactly the right index.
//
int count = 0;
// Iterate over the new list sources...
//
for (int i = 0; i < newListSize; ++i)
{
// If the new value is matched.
//
int newListSource = newListSources[i];
if (newListSource != 0)
{
// Store in the index for where the matched value is now, the index of where it must end up.
//
oldListTargets[newListSource - 1] = count;
// Only increment the count, and hence the index used for moving the items, for matched items.
//
++count;
}
}
// Loop until the termination condition is reached...
//
for (;;)
{
// The index of the value to be moved.
// We're looking for the one that needs to be moved the farthest.
//
int index = -1;
// The distance of the value at the index needs to be moved.
//
int farthest = 0;
// Iterate over the old list targets.
//
for (int i = 0; i < oldListSize; ++i)
{
// Determine the distance between the index and where it should be and ensure it's positive.
//
int distance = oldListTargets[i] - i;
if (distance < 0)
{
distance = -distance;
}
// If the object at this index needs to be moved farther than the one we've determined so far...
//
if (distance > farthest)
{
// Record this index and the distance.
//
index = i;
farthest = distance;
}
}
// If no object needs to be moved.
//
if (index == -1)
{
// Terminate the list.
//
break;
}
else
{
// Keep track of the absolutely correct final target index.
//
int actualTargetIndex = oldListTargets[index];
// Look for an index that might be a better intermediate...
//
int targetIndex = actualTargetIndex;
if (targetIndex < index)
{
// In this case, the value ends up before the index.
// So look to see if the value currently at the target index is before the value we're about to put before it.
//
if (oldListTargets[targetIndex] < actualTargetIndex)
{
do
{
// If so, move it after instead and check again...
//
++targetIndex;
}
while (oldListTargets[targetIndex] < actualTargetIndex);
}
else
{
// Otherwise, check that it wouldn't be better to put the value before the previous target index
// because the value we're about to put after it should be before.
//
while (targetIndex > 0 && oldListTargets[targetIndex - 1] > actualTargetIndex)
{
// If so, move it before instead and check again.
//
--targetIndex;
}
}
}
else
{
// In this case, the value ends up after the index.
// So look to see if the value currently at the target index is after the value we're about to put after it.
//
if (oldListTargets[targetIndex] > actualTargetIndex)
{
do
{
// If so, move it before instead and check again...
//
--targetIndex;
}
while (oldListTargets[targetIndex] > actualTargetIndex);
}
else
{
// Otherwise, check that it wouldn't be better to put the value after the following target index
// because the value we're about to put before it should be after.
//
while (targetIndex + 1 < oldListSize && oldListTargets[targetIndex + 1] < actualTargetIndex)
{
// If so, move it after instead and check again.
//
++targetIndex;
}
}
}
// Move the old value at the index to the best new target index.
//
createMoveListChange(oldList, listChanges, oldList.get(index), index, targetIndex);
// Update the old list targets array to reflect the move.
//
if (targetIndex < index)
{
System.arraycopy(oldListTargets, targetIndex, oldListTargets, targetIndex + 1, index - targetIndex);
}
else
{
System.arraycopy(oldListTargets, index + 1, oldListTargets, index, targetIndex - index);
}
oldListTargets[targetIndex] = actualTargetIndex;
}
}
// If there are objects to add.
//
if (count != newListSize)
{
// Look for them.
//
for (int i = 0; i < newListSize; ++i)
{
int newListSource = newListSources[i];
if (newListSource == 0)
{
// Add the missing new value.
//
createAddListChange(oldList, listChanges, newList.get(i), i);
}
}
}
}
/**
* Used by {@link #createListChanges(EList, EList, EList)} to decide whether the old value is considered equal to the new value.
* @since 2.8
*/
protected boolean equal(Object oldValue, Object newValue)
{
return oldValue == null ? newValue == null : oldValue == newValue || oldValue.equals(newValue);
}
/**
* Convenience method added to allow subclasses to modify the default implementation
* for the scenario in which an element was added to the monitored list.
* @see #createListChanges(EList, EList, EList)
*/
protected void createAddListChange(EList<Object> oldList, EList<ListChange> listChanges, Object newObject, int index)
{
ListChange listChange = createListChange(listChanges, ChangeKind.ADD_LITERAL, index);
listChange.getValues().add(newObject);
oldList.add(index, newObject);
}
/**
* Convenience method added to allow subclasses to modify the default implementation
* for the scenario in which an element was removed from the monitored list.
* @see #createListChanges(EList, EList, EList)
*/
protected void createRemoveListChange(EList<?> oldList, EList<ListChange> listChanges, Object newObject, int index)
{
createListChange(listChanges, ChangeKind.REMOVE_LITERAL, index);
oldList.remove(index);
}
/**
* Convenience method added to allow subclasses to modify the default implementation
* for the scenario in which an element was moved in the monitored list.
* @see #createListChanges(EList, EList, EList)
*/
protected void createMoveListChange(EList<?> oldList, EList<ListChange> listChanges, Object newObject, int index, int toIndex)
{
ListChange listChange = createListChange(listChanges, ChangeKind.MOVE_LITERAL, index);
listChange.setMoveToIndex(toIndex);
oldList.move(toIndex, index);
}
/**
* Creates a ListChange, initializes the main attributes, and adds it to the specified listChanges.
* @param listChanges
* @param kind
* @param index
* @return ListChange
*/
protected ListChange createListChange(EList<ListChange> listChanges, ChangeKind kind, int index)
{
ListChange listChange = ChangeFactory.eINSTANCE.createListChange();
listChange.setKind(kind);
listChange.setIndex(index);
listChanges.add(listChange);
return listChange;
}
}