All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.javagimmicks.collections.diff.DifferenceAlgorithm Maven / Gradle / Ivy

/**
 * 
 */
package net.sf.javagimmicks.collections.diff;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;

class DifferenceAlgorithm
{
	protected final List _fromList;
	protected final List _toList;

	protected final Comparator _comparator;
	protected final DifferenceList _differences = new DefaultDifferenceList();

	protected DefaultDifference _pending;

	protected DifferenceAlgorithm(List fromList, List toList, Comparator comparator)
	{
		_fromList = fromList;
		_toList = toList;
		_comparator = comparator;

		traverseSequences();

		// add the last difference, if pending:
		if (_pending != null)
		{
			_differences.add(_pending);
		}
	}
	
	public DifferenceList getDifferences()
	{
		return _differences;
	}

	/**
	 * Traverses the sequences, seeking the longest common subsequences,
	 * invoking the methods finishedA, finishedB,
	 * onANotB, and onBNotA.
	 */
	protected void traverseSequences()
	{
		List matches = getLongestCommonSubsequences();

		final int lastIndexA = _fromList.size() - 1;
		final int lastIndexB = _toList.size() - 1;
		
		int indexB = 0;
		int indexA;

		int lastMatchIndex = matches.size() - 1;

		for (indexA = 0; indexA <= lastMatchIndex; ++indexA)
		{
			Integer bLine = matches.get(indexA);

			if (bLine == null)
			{
				onANotB(indexA, indexB);
			}
			else
			{
				while (indexB < bLine)
				{
					onBNotA(indexA, indexB++);
				}

				onMatch(indexA, indexB++);
			}
		}

		boolean calledFinishA = false;
		boolean calledFinishB = false;

		while (indexA <= lastIndexA || indexB <= lastIndexB)
		{
			// last A?
			if (indexA == lastIndexA + 1 && indexB <= lastIndexB)
			{
				if (!calledFinishA && callFinishedA())
				{
					finishedA(lastIndexA);
					calledFinishA = true;
				}
				else
				{
					while (indexB <= lastIndexB)
					{
						onBNotA(indexA, indexB++);
					}
				}
			}

			// last B?
			if (indexB == lastIndexB + 1 && indexA <= lastIndexA)
			{
				if (!calledFinishB && callFinishedB())
				{
					finishedB(lastIndexB);
					calledFinishB = true;
				}
				else
				{
					while (indexA <= lastIndexA)
					{
						onANotB(indexA++, indexB);
					}
				}
			}

			if (indexA <= lastIndexA)
			{
				onANotB(indexA++, indexB);
			}

			if (indexB <= lastIndexB)
			{
				onBNotA(indexA, indexB++);
			}
		}
	}

	/**
	 * Override and return true in order to have finishedA
	 * invoked at the last element in the a list.
	 */
	protected boolean callFinishedA()
	{
		return false;
	}

	/**
	 * Override and return true in order to have finishedB
	 * invoked at the last element in the b list.
	 */
	protected boolean callFinishedB()
	{
		return false;
	}

	/**
	 * Invoked at the last element in a, if
	 * callFinishedA returns true.
	 */
	protected void finishedA(int lastA)
	{
	}

	/**
	 * Invoked at the last element in b, if
	 * callFinishedB returns true.
	 */
	protected void finishedB(int lastB)
	{
	}

	/**
	 * Invoked for elements in a and not in b.
	 */
	protected void onANotB(int indexA, int indexB)
	{
		if (_pending == null)
		{
			_pending = new DefaultDifference();
			_pending.setDeleteStartIndex(indexA);
			_pending.setDeleteEndIndex(indexA);
			_pending.setAddStartIndex(indexB);
			_pending.setFromList(_fromList);
			_pending.setToList(_toList);
		}
		else
		{
			setDeleted(_pending, indexA);
		}
	}

	/**
	 * Invoked for elements in b and not in a.
	 */
	protected void onBNotA(int indexA, int indexB)
	{
		if (_pending == null)
		{
			_pending = new DefaultDifference();
			_pending.setDeleteStartIndex(indexA);
			_pending.setAddStartIndex(indexB);
			_pending.setAddEndIndex(indexB);
			_pending.setFromList(_fromList);
			_pending.setToList(_toList);
		}
		else
		{
			setAdded(_pending, indexB);
		}
	}

	/**
	 * Invoked for elements matching in a and b.
	 */
	protected void onMatch(int indexA, int indexB)
	{
		if (_pending != null)
		{
			_differences.add(_pending);
			_pending = null;
		}
	}

	/**
	 * Compares the two objects, using the comparator provided with the
	 * constructor, if any.
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	protected boolean equals(T x, T y)
	{
		if(_comparator == null)
		{
			if(x instanceof Comparable)
			{
				return ((Comparable)x).compareTo(y) == 0;
			}
			else
			{
				return x.equals(y);
			}
		}
		else
		{
			return _comparator.compare(x, y) == 0;
		}
	}

	/**
	 * Returns an array of the longest common subsequences.
	 */
	protected List getLongestCommonSubsequences()
	{
		int startIndexA = 0;
		int endIndexA = _fromList.size() - 1;

		int startIndexB = 0;
		int endIndexB = _toList.size() - 1;

		final TreeMap matches = new TreeMap();

		while (startIndexA <= endIndexA && startIndexB <= endIndexB
				&& equals(_fromList.get(startIndexA), _toList.get(startIndexB)))
		{
			matches.put(startIndexA++, startIndexB++);
		}

		while (startIndexA <= endIndexA && startIndexB <= endIndexB
				&& equals(_fromList.get(endIndexA), _toList.get(endIndexB)))
		{
			matches.put(endIndexA--, endIndexB--);
		}

		final Map> bMatches;
		if(_comparator == null)
		{
			if(_fromList.get(0) instanceof Comparable)
			{
				bMatches = new TreeMap>();
			}
			else
			{
				bMatches = new HashMap>();
			}
		}
		else
		{
			bMatches = new TreeMap>(_comparator);
		}

		for (int indexB = startIndexB; indexB <= endIndexB; ++indexB)
		{
			T key = _toList.get(indexB);
			List positions = bMatches.get(key);

			if (positions == null)
			{
				positions = new ArrayList();
				bMatches.put(key, positions);
			}
			
			positions.add(indexB);
		}

		final TreeMap thresh = new TreeMap();
		final TreeMap links = new TreeMap();

		for (int indexA = startIndexA; indexA <= endIndexA; ++indexA)
		{
			T key = _fromList.get(indexA); // keygen here.
			List positions = bMatches.get(key);

			if (positions == null)
			{
				continue;
			}

			Integer k = 0;

			ListIterator positionIter = positions.listIterator(positions.size());
			while (positionIter.hasPrevious())
			{
				Integer j = positionIter.previous();

				k = insert(thresh, j, k);

				if (k != null)
				{
					Link value = k > 0 ? links.get(k - 1) : null;
					links.put(k, new Link(value, indexA, j));
				}
			}
		}

		if (!thresh.isEmpty())
		{
			for (Link link = links.get(thresh.lastKey()); link != null; link = link._link)
			{
				matches.put(link._x, link._y);
			}
		}

		return toList(matches);
	}
	
	protected static List toList(TreeMap map)
	{
		Integer[] result = new Integer[map.size() == 0 ? 0 : 1 + map.lastKey()];

		for(Entry entry : map.entrySet())
		{
			result[entry.getKey()] = entry.getValue();
		}

		return Arrays.asList(result);
	}

	protected static boolean isNonzero(Integer i)
	{
		return i != null && i != 0;
	}

	protected boolean isGreaterThan(TreeMap thresh, Integer index, Integer val)
	{
		Integer lhs = thresh.get(index);
		return lhs != null && (val != null && lhs > val);
	}

	protected boolean isLessThan(TreeMap thresh, Integer index, Integer val)
	{
		Integer lhs = thresh.get(index);
		return lhs != null && (val == null || lhs < val);
	}

	/**
	 * Adds the given value to the "end" of the threshold map, that is, with the
	 * greatest index/key.
	 */
	protected void append(TreeMap thresh, Integer value)
	{
		int addIdx = thresh.isEmpty() ? 0 : thresh.lastKey() + 1;
		thresh.put(addIdx, value);
	}

	/**
	 * Inserts the given values into the threshold map.
	 */
	protected Integer insert(TreeMap thresh, Integer j, Integer k)
	{
		if (isNonzero(k) && isGreaterThan(thresh, k, j)
				&& isLessThan(thresh, k - 1, j))
		{
			thresh.put(k, j);
		}
		else
		{
			int highIndex = -1;

			if (isNonzero(k))
			{
				highIndex = k;
			}
			else if (!thresh.isEmpty())
			{
				highIndex = thresh.lastKey();
			}

			// off the end?
			if (highIndex == -1 || j.compareTo(thresh.get(thresh.lastKey())) > 0)
			{
				append(thresh, j);
				k = highIndex + 1;
			}
			else
			{
				// binary search for insertion point:
				int lowIndex = 0;

				while (lowIndex <= highIndex)
				{
					int index = (highIndex + lowIndex) / 2;
					Integer val = thresh.get(index);

					int compareResult = j.compareTo(val);
					if (compareResult == 0)
					{
						return null;
					}
					else if (compareResult > 0)
					{
						lowIndex = index + 1;
					}
					else
					{
						highIndex = index - 1;
					}
				}

				thresh.put(lowIndex, j);
				k = lowIndex;
			}
		}

		return k;
	}

	protected static void setDeleted(DefaultDifference d, int index)
	{
		d.setDeleteStartIndex(Math.min(index, d.getDeleteStartIndex()));
		d.setDeleteEndIndex(Math.max(index, d.getDeleteEndIndex()));
	}
	
	protected static void setAdded(DefaultDifference d, int index)
	{
		d.setAddStartIndex(Math.min(index, d.getAddStartIndex()));
		d.setAddEndIndex(Math.max(index, d.getAddEndIndex()));
	}
	
	protected static class Link
    {
		protected Link(Link link, Integer x, Integer y)
    	{
    		_link = link;
    		_x = x;
    		_y = y;
    	}
    	
    	public final Link _link;
    	public final Integer _x;
    	public final Integer _y;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy