Package org.jamwiki.utils

Source Code of org.jamwiki.utils.DiffUtil

/**
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, version 2.1, dated February 1999.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the latest version of the GNU Lesser General
* Public License as published by the Free Software Foundation;
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program (LICENSE.txt); if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/
package org.jamwiki.utils;

import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.StringUtils;
import org.incava.util.diff.Diff;
import org.incava.util.diff.Difference;
import org.jamwiki.model.WikiDiff;

/**
* Utility class for processing the difference between two topics and returing a
* list of WikiDiff objects that can be used to display the diff.
*/
public class DiffUtil {

  private static final WikiLogger logger = WikiLogger.getLogger(DiffUtil.class
      .getName());
  /**
   * The number of lines of unchanged text to display before and after each
   * diff.
   */
  // FIXME - make this a property value
  private static final int DIFF_UNCHANGED_LINE_DISPLAY = 2;
  /** Cache name for the cache of diff information. */
  private static final String CACHE_DIFF_INFORMATION = "org.jamwiki.utils.DiffUtil.CACHE_DIFF_INFORMATION";

  /**
   *
   */
  private DiffUtil() {
  }

  /**
   *
   */
  // private static void addToCache(String newVersion, String oldVersion,
  // List<WikiDiff> results) {
  // String key = generateCacheKey(newVersion, oldVersion);
  // WikiCache.addToCache(CACHE_DIFF_INFORMATION, key, results);
  // }

  /**
   * Utility method for determining whether or not to append lines of context
   * around a diff.
   */
  private static boolean canPostBuffer(Difference nextDiff, int current,
      String[] replacementArray, boolean adding) {
    if (current < 0 || current >= replacementArray.length) {
      // if out of a valid range, don't buffer
      return false;
    }
    if (nextDiff == null) {
      // if in a valid range and no next diff, buffer away
      return true;
    }
    int nextStart = (adding) ? nextDiff.getAddedStart() : nextDiff
        .getDeletedStart();
    // if in a valid range and the next diff starts several lines away, buffer
    // away. otherwise
    // the default is not to diff.
    return (nextStart > current);
  }

  /**
   * Utility method for determining whether or not to prepend lines of context
   * around a diff.
   */
  private static boolean canPreBuffer(Difference previousDiff, int current,
      int currentStart, String[] replacementArray, int bufferAmount,
      boolean adding) {
    if (current < 0 || current >= replacementArray.length) {
      // current position is out of range for buffering
      return false;
    }
    if (previousDiff == null) {
      // if no previous diff, buffer away
      return true;
    }
    if (bufferAmount == -1) {
      // if everything is being buffered and there was a previous diff do not
      // pre-buffer
      return false;
    }
    int previousEnd = (adding) ? previousDiff.getAddedEnd() : previousDiff
        .getDeletedEnd();
    if (previousEnd != -1) {
      // if there was a previous diff but it was several lines previous, buffer
      // away.
      // if there was a previous diff, and it overlaps with the current diff,
      // don't buffer.
      return (current > (previousEnd + bufferAmount));
    }
    int previousStart = (adding) ? previousDiff.getAddedStart() : previousDiff
        .getDeletedStart();
    if (current <= (previousStart + bufferAmount)) {
      // the previous diff did not specify an end, and the current diff would
      // overlap with
      // buffering from its start, don't buffer
      return false;
    }
    // the previous diff did not specify an end, and the current diff will not
    // overlap
    // with buffering from its start, buffer away. otherwise the default is not
    // to buffer.
    return (currentStart > current);
  }

  /**
   * Return a list of WikiDiff objects that can be used to create a display of
   * the diff content.
   *
   * @param newVersion
   *          The String that is to be compared to, ie the later version of a
   *          topic.
   * @param oldVersion
   *          The String that is to be considered as having changed, ie the
   *          earlier version of a topic.
   * @return Returns a list of WikiDiff objects that correspond to the changed
   *         text.
   */
  public static List<WikiDiff> diff(String newVersion, String oldVersion) {
    List<WikiDiff> result = null;
    // List<WikiDiff> result = DiffUtil.retrieveFromCache(newVersion,
    // oldVersion);
    // if (result != null) {
    // return result;
    // }
    String version1 = newVersion;
    String version2 = oldVersion;
    if (version2 == null) {
      version2 = "";
    }
    if (version1 == null) {
      version1 = "";
    }
    // remove line-feeds to avoid unnecessary noise in the diff due to
    // cut & paste or other issues
    version2 = StringUtils.remove(version2, '\r');
    version1 = StringUtils.remove(version1, '\r');
    result = DiffUtil.process(version1, version2);
    // DiffUtil.addToCache(newVersion, oldVersion, result);
    return result;
  }

  /**
   * Generate a mostly-unique key to use for the cache. This key uses the first
   * ten characters of the string and a hash of the full string, which is not
   * guaranteed to be unique but should be unique enough.
   */
  private static String generateCacheKey(String newVersion, String oldVersion) {
    StringBuilder result = new StringBuilder();
    if (newVersion == null) {
      result.append(-1);
    } else if (newVersion.length() <= 10) {
      result.append(newVersion);
    } else {
      result.append(newVersion.substring(0, 10)).append(newVersion.hashCode());
    }
    result.append('-');
    if (oldVersion == null) {
      result.append(-1);
    } else if (oldVersion.length() <= 10) {
      result.append(oldVersion);
    } else {
      result.append(oldVersion.substring(0, 10)).append(oldVersion.hashCode());
    }
    return result.toString();
  }

  /**
   * Format the list of Difference objects into a list of WikiDiff objects,
   * which will include information about what values are different and also
   * include some unchanged values surrounded the changed values, thus giving
   * some context.
   */
  private static List<WikiDiff> generateWikiDiffs(List<Difference> diffs,
      String[] oldArray, String[] newArray) {
    List<WikiDiff> wikiDiffs = new ArrayList<WikiDiff>();
    Difference previousDiff = null;
    Difference nextDiff = null;
    List<WikiDiff> changedLineWikiDiffs = null;
    String[] oldLineArray = null;
    String[] newLineArray = null;
    List<Difference> changedLineDiffs = null;
    List<WikiDiff> wikiSubDiffs = null;
    Difference nextLineDiff = null;
    int i = 0;
    for (Difference currentDiff : diffs) {
      i++;
      wikiDiffs.addAll(DiffUtil.preBufferDifference(currentDiff, previousDiff,
          oldArray, newArray, DIFF_UNCHANGED_LINE_DISPLAY));
      changedLineWikiDiffs = DiffUtil.processDifference(currentDiff, oldArray,
          newArray);
      // loop through the difference and diff the individual lines so that it is
      // possible to highlight the exact
      // text that was changed
      for (WikiDiff changedLineWikiDiff : changedLineWikiDiffs) {
        oldLineArray = DiffUtil.stringToArray(changedLineWikiDiff.getOldText());
        newLineArray = DiffUtil.stringToArray(changedLineWikiDiff.getNewText());
        changedLineDiffs = new Diff<String>(oldLineArray, newLineArray).diff();
        wikiSubDiffs = new ArrayList<WikiDiff>();
        int j = 0;
        for (Difference changedLineDiff : changedLineDiffs) {
          // build sub-diff list, which is the difference for the individual
          // line item
          j++;
          if (j == 1) {
            // pre-buffering is only necessary for the first element as
            // post-buffering
            // will handle all further buffering when bufferAmount is -1.
            wikiSubDiffs.addAll(DiffUtil.preBufferDifference(changedLineDiff,
                null, oldLineArray, newLineArray, -1));
          }
          wikiSubDiffs.addAll(DiffUtil.processDifference(changedLineDiff,
              oldLineArray, newLineArray));
          nextLineDiff = (j < changedLineDiffs.size()) ? changedLineDiffs
              .get(j) : null;
          wikiSubDiffs.addAll(DiffUtil.postBufferDifference(changedLineDiff,
              nextLineDiff, oldLineArray, newLineArray, -1));
        }
        changedLineWikiDiff.setSubDiffs(wikiSubDiffs);
      }
      wikiDiffs.addAll(changedLineWikiDiffs);
      nextDiff = (i < diffs.size()) ? diffs.get(i) : null;
      wikiDiffs.addAll(DiffUtil.postBufferDifference(currentDiff, nextDiff,
          oldArray, newArray, DIFF_UNCHANGED_LINE_DISPLAY));
      previousDiff = currentDiff;
    }
    return wikiDiffs;
  }

  /**
   *
   */
  private static boolean hasMoreDiffInfo(int addedCurrent, int deletedCurrent,
      Difference currentDiff) {
    if (addedCurrent == -1) {
      addedCurrent = 0;
    }
    if (deletedCurrent == -1) {
      deletedCurrent = 0;
    }
    return (addedCurrent <= currentDiff.getAddedEnd() || deletedCurrent <= currentDiff
        .getDeletedEnd());
  }

  /**
   * If possible, append a few lines of unchanged text that appears after to the
   * changed line in order to add context to the current list of WikiDiff
   * objects.
   *
   * @param currentDiff
   *          The current diff object.
   * @param nextDiff
   *          The diff object that immediately follows this object (if any).
   * @param oldArray
   *          The original array of string objects that was compared from in
   *          order to generate the diff.
   * @param newArray
   *          The original array of string objects that was compared to in order
   *          to generate the diff.
   * @param bufferAmount
   *          The number of unchanged elements to display after the diff, or -1
   *          if all unchanged lines should be displayed.
   */
  private static List<WikiDiff> postBufferDifference(Difference currentDiff,
      Difference nextDiff, String[] oldArray, String[] newArray,
      int bufferAmount) {
    List<WikiDiff> wikiDiffs = new ArrayList<WikiDiff>();
    if (bufferAmount == 0) {
      // do not buffer
      return wikiDiffs;
    }
    int deletedCurrent = (currentDiff.getDeletedEnd() == -1) ? currentDiff
        .getDeletedStart() : (currentDiff.getDeletedEnd() + 1);
    int addedCurrent = (currentDiff.getAddedEnd() == -1) ? currentDiff
        .getAddedStart() : (currentDiff.getAddedEnd() + 1);
    int numIterations = bufferAmount;
    if (bufferAmount == -1) {
      // buffer everything
      numIterations = (nextDiff != null) ? Math.max(nextDiff.getAddedStart()
          - addedCurrent, nextDiff.getDeletedStart() - deletedCurrent)
          : Math.max(oldArray.length - deletedCurrent, newArray.length
              - addedCurrent);
    }
    String oldText = null;
    String newText = null;
    for (int i = 0; i < numIterations; i++) {
      int position = (deletedCurrent < 0) ? 0 : deletedCurrent;
      oldText = null;
      newText = null;
      if (canPostBuffer(nextDiff, deletedCurrent, oldArray, false)) {
        oldText = oldArray[deletedCurrent];
        deletedCurrent++;
      }
      if (canPostBuffer(nextDiff, addedCurrent, newArray, true)) {
        newText = newArray[addedCurrent];
        addedCurrent++;
      }
      if (oldText == null && newText == null) {
        logger.fine("Possible DIFF bug: no elements post-buffered.  position: "
            + position + " / deletedCurrent: " + deletedCurrent
            + " / addedCurrent " + addedCurrent + " / numIterations: "
            + numIterations);
        break;
      }
      wikiDiffs.add(new WikiDiff(oldText, newText, position));
    }
    return wikiDiffs;
  }

  /**
   * If possible, prepend a few lines of unchanged text that before after to the
   * changed line in order to add context to the current list of WikiDiff
   * objects.
   *
   * @param currentDiff
   *          The current diff object.
   * @param previousDiff
   *          The diff object that immediately preceded this object (if any).
   * @param oldArray
   *          The original array of string objects that was compared from in
   *          order to generate the diff.
   * @param newArray
   *          The original array of string objects that was compared to in order
   *          to generate the diff.
   * @param bufferAmount
   *          The number of unchanged elements to display after the diff, or -1
   *          if all unchanged lines should be displayed.
   */
  private static List<WikiDiff> preBufferDifference(Difference currentDiff,
      Difference previousDiff, String[] oldArray, String[] newArray,
      int bufferAmount) {
    List<WikiDiff> wikiDiffs = new ArrayList<WikiDiff>();
    if (bufferAmount == 0) {
      return wikiDiffs;
    }
    if (bufferAmount == -1 && previousDiff != null) {
      // when buffering everything, only pre-buffer for the first element as the
      // post-buffer code
      // will handle everything else.
      return wikiDiffs;
    }
    // deletedCurrent is the current position in oldArray to start buffering
    // from
    int deletedCurrent = (bufferAmount == -1 || bufferAmount > currentDiff
        .getDeletedStart()) ? 0
        : (currentDiff.getDeletedStart() - bufferAmount);
    // addedCurrent is the current position in newArray to start buffering from
    int addedCurrent = (bufferAmount == -1 || bufferAmount > currentDiff
        .getAddedStart()) ? 0 : (currentDiff.getAddedStart() - bufferAmount);
    if (previousDiff != null) {
      // if there was a previous diff make sure that it is not being overlapped
      deletedCurrent = Math.max(previousDiff.getDeletedEnd() + 1,
          deletedCurrent);
      addedCurrent = Math.max(previousDiff.getAddedEnd() + 1, addedCurrent);
    }
    // number of iterations is number of loops required to fully buffer the
    // added and deleted diff
    int numIterations = Math.max(
        currentDiff.getDeletedStart() - deletedCurrent, currentDiff
            .getAddedStart()
            - addedCurrent);
    String oldText = null;
    String newText = null;
    for (int i = 0; i < numIterations; i++) {
      int position = (deletedCurrent < 0) ? 0 : deletedCurrent;
      oldText = null;
      newText = null;
      // if diffs are close together, do not allow buffers to overlap
      if (canPreBuffer(previousDiff, deletedCurrent, currentDiff
          .getDeletedStart(), oldArray, bufferAmount, false)) {
        oldText = oldArray[deletedCurrent];
        deletedCurrent++;
      }
      if (canPreBuffer(previousDiff, addedCurrent, currentDiff.getAddedStart(),
          newArray, bufferAmount, true)) {
        newText = newArray[addedCurrent];
        addedCurrent++;
      }
      if (oldText == null && newText == null) {
        logger.fine("Possible DIFF bug: no elements pre-buffered.  position: "
            + position + " / deletedCurrent: " + deletedCurrent
            + " / addedCurrent " + addedCurrent + " / numIterations: "
            + numIterations);
        break;
      }
      wikiDiffs.add(new WikiDiff(oldText, newText, position));
    }
    return wikiDiffs;
  }

  /**
   * @param newVersion
   *          The String that is being compared to.
   * @param oldVersion
   *          The String that is being compared against.
   */
  private static List<WikiDiff> process(String newVersion, String oldVersion) {
    logger.finer("Diffing: " + oldVersion + " against: " + newVersion);
    if (newVersion.equals(oldVersion)) {
      return new ArrayList<WikiDiff>();
    }
    String[] oldArray = DiffUtil.split(oldVersion);
    String[] newArray = DiffUtil.split(newVersion);
    Diff<String> diffObject = new Diff<String>(oldArray, newArray);
    List<Difference> diffs = diffObject.diff();
    return DiffUtil.generateWikiDiffs(diffs, oldArray, newArray);
  }

  /**
   * Process the diff object and add it to the output. Text will either have
   * been deleted or added (it cannot have remained the same, since a diff
   * object represents a change). This method steps through the diff result and
   * converts it into an array of objects that can be used to easily represent
   * the diff.
   */
  private static List<WikiDiff> processDifference(Difference currentDiff,
      String[] oldArray, String[] newArray) {
    List<WikiDiff> wikiDiffs = new ArrayList<WikiDiff>();
    // if text was deleted then deletedCurrent represents the starting position
    // of the deleted text.
    int deletedCurrent = currentDiff.getDeletedStart();
    // if text was added then addedCurrent represents the starting position of
    // the added text.
    int addedCurrent = currentDiff.getAddedStart();
    // count is simply used to ensure that the loop is not infinite, which
    // should never happen
    int count = 0;
    // the text of the element that changed
    String oldText = null;
    // the text of what the element was changed to
    String newText = null;
    while (hasMoreDiffInfo(addedCurrent, deletedCurrent, currentDiff)) {
      // the position within the diff array (line number, character, etc) at
      // which the change
      // started (starting at 0)
      int position = ((deletedCurrent < 0) ? 0 : deletedCurrent);
      oldText = null;
      newText = null;
      if (currentDiff.getDeletedEnd() >= 0
          && currentDiff.getDeletedEnd() >= deletedCurrent) {
        oldText = oldArray[deletedCurrent];
        deletedCurrent++;
      }
      if (currentDiff.getAddedEnd() >= 0
          && currentDiff.getAddedEnd() >= addedCurrent) {
        newText = newArray[addedCurrent];
        addedCurrent++;
      }
      wikiDiffs.add(new WikiDiff(oldText, newText, position));
      // FIXME - this shouldn't be necessary
      count++;
      if (count > 5000) {
        logger.warning("Infinite loop in DiffUtils.processDifference");
        break;
      }
    }
    return wikiDiffs;
  }

  /**
   * Determine if diff information is available in the cache. If so return it,
   * otherwise return <code>null</code>.
   */
  // private static List<WikiDiff> retrieveFromCache(String newVersion, String
  // oldVersion) {
  // String key = generateCacheKey(newVersion, oldVersion);
  // Element cachedDiffInformation =
  // WikiCache.retrieveFromCache(CACHE_DIFF_INFORMATION, key);
  // return (cachedDiffInformation != null) ?
  // (List<WikiDiff>)cachedDiffInformation.getObjectValue() : null;
  // }

  /**
   * Split up a String into an array of values using the specified string
   * pattern.
   *
   * @param original
   *          The value that is being split.
   */
  private static String[] split(String original) {
    if (original == null) {
      return new String[0];
    }
    return original.split("\n");
  }

  /**
   * Convert a string to a string array of characters.
   *
   * @param original
   *          The value that is being split.
   */
  private static String[] stringToArray(String original) {
    if (original == null) {
      return new String[0];
    }
    String[] result = new String[original.length()];
    for (int i = 0; i < result.length; i++) {
      result[i] = String.valueOf(original.charAt(i));
    }
    return result;
  }
}
TOP

Related Classes of org.jamwiki.utils.DiffUtil

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.