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

rcumflex.circumflex-diff.3.0-RC1.source-code.myers.scala Maven / Gradle / Ivy

The newest version!
package pro.savant.circumflex
package diff

import collection.mutable.ListBuffer

/*! # Myers diff algorithm

`MyersDiff` computes the difference between two collections of arbitrary
typed elements. Elements are tested for equality using their `equals` method.

The result is reported as a collection of `Chunk`s
(see [[/diff/src/main/scala/chunk.scala]]).

## Usage

``` {.scala}
val a = Seq("a", "b", "c")
val b = Seq("b", "d", "c")

new MyersDiff[String].diff(a, b)
```
*/
class MyersDiff[T] {

  def diff(original: Seq[T], revised: Seq[T]): Difference[T] = try {
    val path = buildPath(original, revised)
    buildRevision(path, original, revised)
  } catch {
    case e: Exception =>
      DIFF_LOG.error("Diff failed.", e)
      throw e
  }

  def buildPath(original: Seq[T], revised: Seq[T]): PathNode = {
    val N = original.length
    val M = revised.length
    val MAX = M + N + 1
    val size = 1 + 2 * MAX
    val middle = size / 2
    val diagonal = new Array[PathNode](size)

    diagonal(middle + 1) = new Snake(0, -1, None)
    0.until(MAX).map { d =>
      (-d).to(d, 2).map { k =>
        val kmiddle = middle + k
        val kplus = kmiddle + 1
        val kminus = kmiddle -1

        var prev: Option[PathNode] = None
        var i: Int =
          if ((k == -d) || (k != d && diagonal(kminus).i < diagonal(kplus).i)) {
            prev = if (diagonal(kplus) == null) None else Some(diagonal(kplus))
            diagonal(kplus).i
          } else {
            prev = if (diagonal(kminus) == null) None else Some(diagonal(kminus))
            diagonal(kminus).i + 1
          }
        diagonal(kminus) = null
        var j = i - k
        var node: PathNode = new DiffNode(i, j, prev)

        while (i < N && j < M && (original(i) == revised(j))) {
          i += 1
          j += 1
        }

        if (i > node.i)
          node = new Snake(i, j, Some(node))

        diagonal(kmiddle) = node

        if (i >= N && j >= M)
          return diagonal(kmiddle)
      }
      diagonal(middle + d - 1) = null
    }
    // According to Myers, this cannot happen
    throw new DiffFailedException("Could not find a diff path")
  }

  def buildRevision(path: PathNode, original: Seq[T], revised: Seq[T]) = {

    val chunks = new ListBuffer[Chunk[T]]

    var pathOpt: Option[PathNode] = Some(path)
    if (path.isSnake) {
      val c: Chunk[T] = new EqualChunk[T](copyOfRange(original, path.prev.get.i, path.i),
        copyOfRange(revised, path.prev.get.j, path.j))
      chunks += c
      pathOpt = path.prev
    }
    while (!pathOpt.isEmpty
        && !pathOpt.get.prev.isEmpty
        && pathOpt.get.prev.get.j >= 0) {
      var path = pathOpt.get
      if (path.isSnake)
        throw new IllegalStateException("bad diffpath: found snake when looking for diff")
      val i = path.i
      val j = path.j
      path = path.prev.get

      val origSeq = copyOfRange(original, path.i, i)
      val revSeq = copyOfRange(revised, path.j, j)
      val c: Chunk[T] =
        if (origSeq.size == 0 && revSeq.size != 0)
          new InsertChunk[T](origSeq, revSeq)
        else if (origSeq.size > 0 && revSeq.size == 0)
          new DeleteChunk[T](origSeq, revSeq)
        else new ChangeChunk[T](origSeq, revSeq)
      chunks += c
      if (path.isSnake) {
        val c: Chunk[T] = new EqualChunk[T](copyOfRange(original, path.prev.get.i, path.i),
          copyOfRange(revised, path.prev.get.j, path.j))
        chunks += c
        pathOpt = path.prev
      } else {
        pathOpt = Some(path)
      }
    }
    new Difference[T](chunks.reverse)
  }

  def copyOfRange(original: Seq[T],
                  from: Int, to: Int): Seq[T] = {
    val buffer = new ListBuffer[T]
    val newLength = to - from
    if (newLength < 0)
      throw new IllegalArgumentException(from + " > " + to)
    val len = math.min(original.length - from, newLength)
    buffer ++= original.drop(from).take(len)
    buffer.toSeq
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy