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

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

There is a newer version: 0.99-alpha1
Show newest version
/**
 * 
 */
package net.sf.javagimmicks.collections.diff;

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

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

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

   protected DefaultDifference _pending;

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

      traverseSequences();

      // add the last difference, if pending:
      if (_pending != null)
      {
         _differences.getDecorated().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()
   {
      final List matches = getLongestCommonSubsequences();

      if (Thread.currentThread().isInterrupted())
      {
         return;
      }

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

      int indexB = 0;
      int indexA;

      final int lastMatchIndex = matches.size() - 1;

      for (indexA = 0; indexA <= lastMatchIndex; ++indexA)
      {
         final 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(final int lastA)
   {}

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

   /**
    * Invoked for elements in a and not in b.
    */
   protected void onANotB(final int indexA, final 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(final int indexA, final 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(final int indexA, final int indexB)
   {
      if (_pending != null)
      {
         _differences.getDecorated().add(_pending);
         _pending = null;
      }
   }

   /**
    * Compares the two objects, using the comparator provided with the
    * constructor, if any.
    */
   @SuppressWarnings({ "unchecked", "rawtypes" })
   protected boolean equals(final T x, final 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()
   {
      final Thread currentThread = Thread.currentThread();

      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)))
      {
         if (currentThread.isInterrupted())
         {
            return Collections.emptyList();
         }

         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)
      {
         if (currentThread.isInterrupted())
         {
            return Collections.emptyList();
         }

         final 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)
      {
         if (currentThread.isInterrupted())
         {
            return Collections.emptyList();
         }

         final T key = _fromList.get(indexA); // keygen here.
         final List positions = bMatches.get(key);

         if (positions == null)
         {
            continue;
         }

         Integer k = 0;

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

            k = insert(thresh, j, k);

            if (k != null)
            {
               final 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(final TreeMap map)
   {
      final Integer[] result = new Integer[map.size() == 0 ? 0 : 1 + map.lastKey()];

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

      return Arrays.asList(result);
   }

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

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

   protected boolean isLessThan(final TreeMap thresh, final Integer index, final Integer val)
   {
      final 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(final TreeMap thresh, final Integer value)
   {
      final int addIdx = thresh.isEmpty() ? 0 : thresh.lastKey() + 1;
      thresh.put(addIdx, value);
   }

   /**
    * Inserts the given values into the threshold map.
    */
   protected Integer insert(final TreeMap thresh, final 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)
            {
               final int index = (highIndex + lowIndex) / 2;
               final Integer val = thresh.get(index);

               final 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(final DefaultDifference d, final int index)
   {
      d.setDeleteStartIndex(Math.min(index, d._deleteStartIndex));
      d.setDeleteEndIndex(Math.max(index, d._deleteEndIndex));
   }

   protected static void setAdded(final DefaultDifference d, final int index)
   {
      d.setAddStartIndex(Math.min(index, d._addStartIndex));
      d.setAddEndIndex(Math.max(index, d._addEndIndex));
   }

   protected static class Link
   {
      protected Link(final Link link, final Integer x, final 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