initial commit: some diff algorithm
This commit is contained in:
commit
3f6a16224e
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
/.gradle/
|
||||
/.idea/
|
||||
/build/
|
7
LICENSE.md
Normal file
7
LICENSE.md
Normal file
@ -0,0 +1,7 @@
|
||||
Copyright 2023 Sergey S. Chernov
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
9
README.md
Normal file
9
README.md
Normal file
@ -0,0 +1,9 @@
|
||||
# 3-way merge
|
||||
|
||||
Tho tools to make smart merge of two versions of changed original data. See [merge3] function online docs.
|
||||
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
This work is based on the original kotlin diff port https://github.com/GitLiveApp/kotlin-diff-utils work, unfortunatley its packaging is not compatible with current kotlin MP formats so I can't just use it as the dependencies. The wholde `dev.gitlive.difflib` is taken from the link above, where it was published under the [Apache 2.0 license at the moment of borrowing this code](https://github.com/GitLiveApp/kotlin-diff-utils/blob/master/LICENSE).
|
||||
|
60
build.gradle.kts
Normal file
60
build.gradle.kts
Normal file
@ -0,0 +1,60 @@
|
||||
plugins {
|
||||
kotlin("multiplatform") version "1.8.0"
|
||||
}
|
||||
|
||||
group = "net.sergeych"
|
||||
version = "0.0.1-SNAPSHOT"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://maven.universablockchain.com")
|
||||
}
|
||||
|
||||
kotlin {
|
||||
jvm {
|
||||
jvmToolchain(8)
|
||||
withJava()
|
||||
testRuns["test"].executionTask.configure {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
js(IR) {
|
||||
browser {
|
||||
commonWebpackConfig {
|
||||
// cssSupport {
|
||||
// enabled.set(true)
|
||||
// }
|
||||
}
|
||||
}
|
||||
}
|
||||
val hostOs = System.getProperty("os.name")
|
||||
val isMingwX64 = hostOs.startsWith("Windows")
|
||||
val nativeTarget = when {
|
||||
hostOs == "Mac OS X" -> macosX64("native")
|
||||
hostOs == "Linux" -> linuxX64("native")
|
||||
isMingwX64 -> mingwX64("native")
|
||||
else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
|
||||
}
|
||||
|
||||
|
||||
sourceSets {
|
||||
val commonMain by getting {
|
||||
dependencies {
|
||||
implementation("dev.gitlive:kotlin-diff-utils:4.1.4")
|
||||
implementation("net.sergeych:mp_stools:[1.3.3,)")
|
||||
|
||||
}
|
||||
}
|
||||
val commonTest by getting {
|
||||
dependencies {
|
||||
implementation(kotlin("test"))
|
||||
}
|
||||
}
|
||||
val jvmMain by getting
|
||||
val jvmTest by getting
|
||||
val jsMain by getting
|
||||
val jsTest by getting
|
||||
val nativeMain by getting
|
||||
val nativeTest by getting
|
||||
}
|
||||
}
|
2
gradle.properties
Normal file
2
gradle.properties
Normal file
@ -0,0 +1,2 @@
|
||||
kotlin.code.style=official
|
||||
kotlin.js.compiler=ir
|
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
5
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
3
settings.gradle.kts
Normal file
3
settings.gradle.kts
Normal file
@ -0,0 +1,3 @@
|
||||
|
||||
rootProject.name = "mp_diff"
|
||||
|
218
src/commonMain/kotlin/dev/gitlive/difflib/DiffUtils.kt
Normal file
218
src/commonMain/kotlin/dev/gitlive/difflib/DiffUtils.kt
Normal file
@ -0,0 +1,218 @@
|
||||
/*
|
||||
* Copyright 2009-2017 java-diff-utils.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dev.gitlive.difflib
|
||||
|
||||
import dev.gitlive.difflib.algorithm.DiffAlgorithmFactory
|
||||
import dev.gitlive.difflib.algorithm.DiffAlgorithmI
|
||||
import dev.gitlive.difflib.algorithm.DiffAlgorithmListener
|
||||
import dev.gitlive.difflib.algorithm.myers.MeyersDiff
|
||||
import dev.gitlive.difflib.patch.Patch
|
||||
import dev.gitlive.difflib.patch.PatchFailedException
|
||||
|
||||
internal typealias Predicate<T> = (T) -> Boolean
|
||||
internal typealias BiPredicate<T, R> = (T, R) -> Boolean
|
||||
internal typealias Consumer<T> = (T) -> Unit
|
||||
internal typealias Function<T, R> = (T) -> R
|
||||
internal typealias BiFunction<T, T2, R> = (T, T2) -> R
|
||||
internal typealias BiConsumer<T, U> = (T, U) -> Unit
|
||||
internal typealias LineReader = suspend () -> String?
|
||||
|
||||
/**
|
||||
* Implements the difference and patching engine
|
||||
*/
|
||||
object DiffUtils {
|
||||
/**
|
||||
* This factory generates the DEFAULT_DIFF algorithm for all these routines.
|
||||
*/
|
||||
var DEFAULT_DIFF: DiffAlgorithmFactory = MeyersDiff.Companion.factory()
|
||||
@kotlin.jvm.JvmStatic
|
||||
fun withDefaultDiffAlgorithmFactory(factory: DiffAlgorithmFactory) {
|
||||
DEFAULT_DIFF = factory
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the difference between the original and revised list of elements
|
||||
* with default diff algorithm
|
||||
*
|
||||
* @param <T> types to be diffed
|
||||
* @param original The original text. Must not be `null`.
|
||||
* @param revised The revised text. Must not be `null`.
|
||||
* @param progress progress listener
|
||||
* @return The patch describing the difference between the original and
|
||||
* revised sequences. Never `null`.
|
||||
</T> */
|
||||
fun <T> diff(original: List<T>, revised: List<T>, progress: DiffAlgorithmListener?): Patch<T> {
|
||||
return diff<T>(original, revised, DEFAULT_DIFF.create(), progress)
|
||||
}
|
||||
|
||||
fun <T> diff(original: List<T>, revised: List<T>): Patch<T> {
|
||||
return diff<T>(original, revised, DEFAULT_DIFF.create(), null)
|
||||
}
|
||||
|
||||
fun <T> diff(original: List<T>, revised: List<T>, includeEqualParts: Boolean): Patch<T> {
|
||||
return diff<T>(original, revised, DEFAULT_DIFF.create(), null, includeEqualParts)
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the difference between the original and revised text.
|
||||
*/
|
||||
fun diff(
|
||||
sourceText: String, targetText: String,
|
||||
progress: DiffAlgorithmListener?
|
||||
): Patch<String> {
|
||||
return diff(sourceText.trimEnd('\n').lines(), targetText.trimEnd('\n').lines(), progress)
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the difference between the original and revised list of elements
|
||||
* with default diff algorithm
|
||||
*
|
||||
* @param source The original text. Must not be `null`.
|
||||
* @param target The revised text. Must not be `null`.
|
||||
*
|
||||
* @param equalizer the equalizer object to replace the default compare
|
||||
* algorithm (Object.equals). If `null` the default equalizer of the
|
||||
* default algorithm is used..
|
||||
* @return The patch describing the difference between the original and
|
||||
* revised sequences. Never `null`.
|
||||
*/
|
||||
fun <T> diff(
|
||||
source: List<T>, target: List<T>,
|
||||
equalizer: BiPredicate<T, T>?
|
||||
): Patch<T> {
|
||||
return if (equalizer != null) {
|
||||
diff(
|
||||
source, target,
|
||||
DEFAULT_DIFF.create(equalizer)
|
||||
)
|
||||
} else diff(source, target, MeyersDiff())
|
||||
}
|
||||
|
||||
fun <T> diff(
|
||||
source: List<T>, target: List<T>,
|
||||
includeEqualParts: Boolean,
|
||||
equalizer: BiPredicate<T, T>?
|
||||
): Patch<T> {
|
||||
return if (equalizer != null) {
|
||||
diff(source, target, DEFAULT_DIFF.create(equalizer), null, includeEqualParts)
|
||||
} else diff(source, target, MeyersDiff())
|
||||
}
|
||||
|
||||
fun <T> diff(
|
||||
original: List<T>, revised: List<T>,
|
||||
algorithm: DiffAlgorithmI<T>, progress: DiffAlgorithmListener?
|
||||
): Patch<T> {
|
||||
return diff(original, revised, algorithm, progress, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the difference between the original and revised list of elements
|
||||
* with default diff algorithm
|
||||
*
|
||||
* @param original The original text. Must not be `null`.
|
||||
* @param revised The revised text. Must not be `null`.
|
||||
* @param algorithm The diff algorithm. Must not be `null`.
|
||||
* @param progress The diff algorithm listener.
|
||||
* @param includeEqualParts Include equal data parts into the patch.
|
||||
* @return The patch describing the difference between the original and
|
||||
* revised sequences. Never `null`.
|
||||
*/
|
||||
fun <T> diff(
|
||||
original: List<T>, revised: List<T>,
|
||||
algorithm: DiffAlgorithmI<T>, progress: DiffAlgorithmListener?,
|
||||
includeEqualParts: Boolean
|
||||
): Patch<T> {
|
||||
return Patch.Companion.generate<T>(
|
||||
original,
|
||||
revised,
|
||||
algorithm.computeDiff(original, revised, progress),
|
||||
includeEqualParts
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the difference between the original and revised list of elements
|
||||
* with default diff algorithm
|
||||
*
|
||||
* @param original The original text. Must not be `null`.
|
||||
* @param revised The revised text. Must not be `null`.
|
||||
* @param algorithm The diff algorithm. Must not be `null`.
|
||||
* @return The patch describing the difference between the original and
|
||||
* revised sequences. Never `null`.
|
||||
*/
|
||||
@kotlin.jvm.JvmStatic
|
||||
fun <T> diff(original: List<T>, revised: List<T>, algorithm: DiffAlgorithmI<T>): Patch<T> {
|
||||
return diff(original, revised, algorithm, null)
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the difference between the given texts inline. This one uses the
|
||||
* "trick" to make out of texts lists of characters, like DiffRowGenerator
|
||||
* does and merges those changes at the end together again.
|
||||
*
|
||||
* @param original
|
||||
* @param revised
|
||||
* @return
|
||||
*/
|
||||
@kotlin.jvm.JvmStatic
|
||||
fun diffInline(original: String, revised: String): Patch<String> {
|
||||
val origList: MutableList<String> = ArrayList()
|
||||
val revList: MutableList<String> = ArrayList()
|
||||
for (character in original) {
|
||||
origList.add(character.toString())
|
||||
}
|
||||
for (character in revised) {
|
||||
revList.add(character.toString())
|
||||
}
|
||||
val patch: Patch<String> = diff(origList, revList)
|
||||
for (delta in patch.getDeltas()) {
|
||||
delta.source.lines = compressLines(delta.source.lines, "")
|
||||
delta.target.lines = compressLines(delta.target.lines, "")
|
||||
}
|
||||
return patch
|
||||
}
|
||||
|
||||
private fun compressLines(lines: List<String>, delimiter: String): List<String> {
|
||||
return if (lines.isEmpty()) {
|
||||
emptyList<String>()
|
||||
} else listOf<String>(lines.joinToString(delimiter))
|
||||
}
|
||||
|
||||
/**
|
||||
* Patch the original text with given patch
|
||||
*
|
||||
* @param original the original text
|
||||
* @param patch the given patch
|
||||
* @return the revised text
|
||||
* @throws PatchFailedException if can't apply patch
|
||||
*/
|
||||
@kotlin.jvm.JvmStatic
|
||||
// @Throws(PatchFailedException::class)
|
||||
fun <T> patch(original: List<T>, patch: Patch<T>): List<T> {
|
||||
return patch.applyTo(original)
|
||||
}
|
||||
|
||||
/**
|
||||
* Unpatch the revised text for a given patch
|
||||
*
|
||||
* @param revised the revised text
|
||||
* @param patch the given patch
|
||||
* @return the original text
|
||||
*/
|
||||
fun <T> unpatch(revised: List<T>, patch: Patch<T>): List<T> {
|
||||
return patch.restore(revised)
|
||||
}
|
||||
}
|
311
src/commonMain/kotlin/dev/gitlive/difflib/UnifiedDiffUtils.kt
Normal file
311
src/commonMain/kotlin/dev/gitlive/difflib/UnifiedDiffUtils.kt
Normal file
@ -0,0 +1,311 @@
|
||||
/*
|
||||
* Copyright 2017 java-diff-utils.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dev.gitlive.difflib
|
||||
|
||||
import dev.gitlive.difflib.patch.AbstractDelta
|
||||
import dev.gitlive.difflib.patch.ChangeDelta
|
||||
import dev.gitlive.difflib.patch.Chunk
|
||||
import dev.gitlive.difflib.patch.Patch
|
||||
|
||||
/**
|
||||
*
|
||||
* @author toben
|
||||
*/
|
||||
object UnifiedDiffUtils {
|
||||
private val UNIFIED_DIFF_CHUNK_REGEXP =
|
||||
Regex("^@@\\s+-(?:(\\d+)(?:,(\\d+))?)\\s+\\+(?:(\\d+)(?:,(\\d+))?)\\s+@@$")
|
||||
private const val NULL_FILE_INDICATOR = "/dev/null"
|
||||
|
||||
/**
|
||||
* Parse the given text in unified format and creates the list of deltas for it.
|
||||
*
|
||||
* @param diff the text in unified format
|
||||
* @return the patch with deltas.
|
||||
*/
|
||||
@kotlin.jvm.JvmStatic
|
||||
fun parseUnifiedDiff(diff: List<String>): Patch<String> {
|
||||
var inPrelude = true
|
||||
val rawChunk: MutableList<Array<String>> = ArrayList()
|
||||
val patch = Patch<String>()
|
||||
var old_ln = 0
|
||||
var new_ln = 0
|
||||
var tag: String
|
||||
var rest: String
|
||||
for (line in diff) {
|
||||
// Skip leading lines until after we've seen one starting with '+++'
|
||||
if (inPrelude) {
|
||||
if (line.startsWith("+++")) {
|
||||
inPrelude = false
|
||||
}
|
||||
continue
|
||||
}
|
||||
val m = UNIFIED_DIFF_CHUNK_REGEXP.find(line)
|
||||
if (m != null) {
|
||||
// Process the lines in the previous chunk
|
||||
processLinesInPrevChunk(rawChunk, patch, old_ln, new_ln)
|
||||
// Parse the @@ header
|
||||
old_ln = if (m.groups[1] == null) 1 else m.groupValues[1].toInt()
|
||||
new_ln = if (m.groups[3] == null) 1 else m.groupValues[3].toInt()
|
||||
if (old_ln == 0) {
|
||||
old_ln = 1
|
||||
}
|
||||
if (new_ln == 0) {
|
||||
new_ln = 1
|
||||
}
|
||||
} else {
|
||||
if (line.length > 0) {
|
||||
tag = line.substring(0, 1)
|
||||
rest = line.substring(1)
|
||||
if (" " == tag || "+" == tag || "-" == tag) {
|
||||
rawChunk.add(arrayOf(tag, rest))
|
||||
}
|
||||
} else {
|
||||
rawChunk.add(arrayOf(" ", ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process the lines in the last chunk
|
||||
processLinesInPrevChunk(rawChunk, patch, old_ln, new_ln)
|
||||
return patch
|
||||
}
|
||||
|
||||
private fun processLinesInPrevChunk(
|
||||
rawChunk: MutableList<Array<String>>,
|
||||
patch: Patch<String>,
|
||||
old_ln: Int,
|
||||
new_ln: Int
|
||||
) {
|
||||
var tag: String
|
||||
var rest: String
|
||||
if (!rawChunk.isEmpty()) {
|
||||
val oldChunkLines: MutableList<String> = ArrayList()
|
||||
val newChunkLines: MutableList<String> = ArrayList()
|
||||
val removePosition: MutableList<Int> = ArrayList()
|
||||
val addPosition: MutableList<Int> = ArrayList()
|
||||
var removeNum = 0
|
||||
var addNum = 0
|
||||
for (raw_line in rawChunk) {
|
||||
tag = raw_line[0]
|
||||
rest = raw_line[1]
|
||||
if (" " == tag || "-" == tag) {
|
||||
removeNum++
|
||||
oldChunkLines.add(rest)
|
||||
if ("-" == tag) {
|
||||
removePosition.add(old_ln - 1 + removeNum)
|
||||
}
|
||||
}
|
||||
if (" " == tag || "+" == tag) {
|
||||
addNum++
|
||||
newChunkLines.add(rest)
|
||||
if ("+" == tag) {
|
||||
addPosition.add(new_ln - 1 + addNum)
|
||||
}
|
||||
}
|
||||
}
|
||||
patch.addDelta(
|
||||
ChangeDelta(
|
||||
Chunk(
|
||||
old_ln - 1, oldChunkLines, removePosition
|
||||
), Chunk(
|
||||
new_ln - 1, newChunkLines, addPosition
|
||||
)
|
||||
)
|
||||
)
|
||||
rawChunk.clear()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* generateUnifiedDiff takes a Patch and some other arguments, returning the Unified Diff format
|
||||
* text representing the Patch. Author: Bill James (tankerbay@gmail.com).
|
||||
*
|
||||
* @param originalFileName - Filename of the original (unrevised file)
|
||||
* @param revisedFileName - Filename of the revised file
|
||||
* @param originalLines - Lines of the original file
|
||||
* @param patch - Patch created by the diff() function
|
||||
* @param contextSize - number of lines of context output around each difference in the file.
|
||||
* @return List of strings representing the Unified Diff representation of the Patch argument.
|
||||
*/
|
||||
@kotlin.jvm.JvmStatic
|
||||
fun generateUnifiedDiff(
|
||||
originalFileName: String?,
|
||||
revisedFileName: String?, originalLines: List<String>, patch: Patch<String>,
|
||||
contextSize: Int
|
||||
): List<String> {
|
||||
if (!patch.getDeltas().isEmpty()) {
|
||||
val ret: MutableList<String> = ArrayList()
|
||||
ret.add("--- ${originalFileName ?: NULL_FILE_INDICATOR}")
|
||||
ret.add("+++ ${revisedFileName ?: NULL_FILE_INDICATOR}")
|
||||
val patchDeltas: List<AbstractDelta<String>> = ArrayList<AbstractDelta<String>>(
|
||||
patch.getDeltas()
|
||||
)
|
||||
|
||||
// code outside the if block also works for single-delta issues.
|
||||
val deltas: MutableList<AbstractDelta<String>> = ArrayList() // current
|
||||
// list
|
||||
// of
|
||||
// Delta's to
|
||||
// process
|
||||
var delta = patchDeltas[0]
|
||||
deltas.add(delta) // add the first Delta to the current set
|
||||
// if there's more than 1 Delta, we may need to output them together
|
||||
if (patchDeltas.size > 1) {
|
||||
for (i in 1 until patchDeltas.size) {
|
||||
val position = delta.source.position // store
|
||||
// the
|
||||
// current
|
||||
// position
|
||||
// of
|
||||
// the first Delta
|
||||
|
||||
// Check if the next Delta is too close to the current
|
||||
// position.
|
||||
// And if it is, add it to the current set
|
||||
val nextDelta = patchDeltas[i]
|
||||
if (position + delta.source.size() + contextSize >= nextDelta.source.position - contextSize) {
|
||||
deltas.add(nextDelta)
|
||||
} else {
|
||||
// if it isn't, output the current set,
|
||||
// then create a new set and add the current Delta to
|
||||
// it.
|
||||
val curBlock = processDeltas(
|
||||
originalLines,
|
||||
deltas, contextSize, false
|
||||
)
|
||||
ret.addAll(curBlock)
|
||||
deltas.clear()
|
||||
deltas.add(nextDelta)
|
||||
}
|
||||
delta = nextDelta
|
||||
}
|
||||
}
|
||||
// don't forget to process the last set of Deltas
|
||||
val curBlock = processDeltas(
|
||||
originalLines, deltas,
|
||||
contextSize, patchDeltas.size == 1 && originalFileName == null
|
||||
)
|
||||
ret.addAll(curBlock)
|
||||
return ret
|
||||
}
|
||||
return ArrayList()
|
||||
}
|
||||
|
||||
/**
|
||||
* processDeltas takes a list of Deltas and outputs them together in a single block of
|
||||
* Unified-Diff-format text. Author: Bill James (tankerbay@gmail.com).
|
||||
*
|
||||
* @param origLines - the lines of the original file
|
||||
* @param deltas - the Deltas to be output as a single block
|
||||
* @param contextSize - the number of lines of context to place around block
|
||||
* @return
|
||||
*/
|
||||
private fun processDeltas(
|
||||
origLines: List<String>,
|
||||
deltas: List<AbstractDelta<String>>, contextSize: Int, newFile: Boolean
|
||||
): List<String> {
|
||||
val buffer: MutableList<String> = ArrayList()
|
||||
var origTotal = 0 // counter for total lines output from Original
|
||||
var revTotal = 0 // counter for total lines output from Original
|
||||
var line: Int
|
||||
var curDelta = deltas[0]
|
||||
var origStart: Int
|
||||
if (newFile) {
|
||||
origStart = 0
|
||||
} else {
|
||||
// NOTE: +1 to overcome the 0-offset Position
|
||||
origStart = curDelta.source.position + 1 - contextSize
|
||||
if (origStart < 1) {
|
||||
origStart = 1
|
||||
}
|
||||
}
|
||||
var revStart = curDelta.target.position + 1 - contextSize
|
||||
if (revStart < 1) {
|
||||
revStart = 1
|
||||
}
|
||||
|
||||
// find the start of the wrapper context code
|
||||
var contextStart = curDelta.source.position - contextSize
|
||||
if (contextStart < 0) {
|
||||
contextStart = 0 // clamp to the start of the file
|
||||
}
|
||||
|
||||
// output the context before the first Delta
|
||||
line = contextStart
|
||||
while (line < curDelta.source.position) {
|
||||
buffer.add(" " + origLines[line])
|
||||
origTotal++
|
||||
revTotal++
|
||||
line++
|
||||
}
|
||||
|
||||
// output the first Delta
|
||||
buffer.addAll(getDeltaText(curDelta))
|
||||
origTotal += curDelta.source.lines.size
|
||||
revTotal += curDelta.target.lines.size
|
||||
var deltaIndex = 1
|
||||
while (deltaIndex < deltas.size) { // for each of the other Deltas
|
||||
val nextDelta = deltas[deltaIndex]
|
||||
val intermediateStart = (curDelta.source.position + curDelta.source.lines.size)
|
||||
line = intermediateStart
|
||||
while (line < nextDelta.source.position) {
|
||||
// output the code between the last Delta and this one
|
||||
buffer.add(" " + origLines[line])
|
||||
origTotal++
|
||||
revTotal++
|
||||
line++
|
||||
}
|
||||
buffer.addAll(getDeltaText(nextDelta)) // output the Delta
|
||||
origTotal += nextDelta.source.lines.size
|
||||
revTotal += nextDelta.target.lines.size
|
||||
curDelta = nextDelta
|
||||
deltaIndex++
|
||||
}
|
||||
|
||||
// Now output the post-Delta context code, clamping the end of the file
|
||||
contextStart = (curDelta.source.position + curDelta.source.lines.size)
|
||||
line = contextStart
|
||||
while (line < contextStart + contextSize
|
||||
&& line < origLines.size
|
||||
) {
|
||||
buffer.add(" " + origLines[line])
|
||||
origTotal++
|
||||
revTotal++
|
||||
line++
|
||||
}
|
||||
|
||||
// Create and insert the block header, conforming to the Unified Diff standard
|
||||
buffer.add(0, "@@ -$origStart,$origTotal +$revStart,$revTotal @@")
|
||||
return buffer
|
||||
}
|
||||
|
||||
/**
|
||||
* getDeltaText returns the lines to be added to the Unified Diff text from the Delta parameter. Author: Bill James (tankerbay@gmail.com).
|
||||
*
|
||||
* @param delta - the Delta to output
|
||||
* @return list of String lines of code.
|
||||
*/
|
||||
private fun getDeltaText(delta: AbstractDelta<String>): List<String> {
|
||||
val buffer: MutableList<String> = ArrayList()
|
||||
for (line in delta.source.lines) {
|
||||
buffer.add("-$line")
|
||||
}
|
||||
for (line in delta.target.lines) {
|
||||
buffer.add("+$line")
|
||||
}
|
||||
return buffer
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright 2017 java-diff-utils.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dev.gitlive.difflib.algorithm
|
||||
|
||||
import dev.gitlive.difflib.patch.DeltaType
|
||||
|
||||
/**
|
||||
*
|
||||
* @author [Tobias Warneke](t.warneke@gmx.net)
|
||||
*/
|
||||
class Change(
|
||||
val deltaType: DeltaType,
|
||||
val startOriginal: Int,
|
||||
val endOriginal: Int,
|
||||
val startRevised: Int,
|
||||
val endRevised: Int
|
||||
) {
|
||||
fun withEndOriginal(endOriginal: Int): Change {
|
||||
return Change(deltaType, startOriginal, endOriginal, startRevised, endRevised)
|
||||
}
|
||||
|
||||
fun withEndRevised(endRevised: Int): Change {
|
||||
return Change(deltaType, startOriginal, endOriginal, startRevised, endRevised)
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright 2021 java-diff-utils.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dev.gitlive.difflib.algorithm
|
||||
|
||||
import dev.gitlive.difflib.BiPredicate
|
||||
|
||||
/**
|
||||
* Tool to create new instances of a diff algorithm. This one is only needed at the moment to
|
||||
* set DiffUtils default diff algorithm.
|
||||
* @author tw
|
||||
*/
|
||||
interface DiffAlgorithmFactory {
|
||||
fun <T> create(): DiffAlgorithmI<T>
|
||||
fun <T> create(equalizer: BiPredicate<T, T>): DiffAlgorithmI<T>
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2018 java-diff-utils.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dev.gitlive.difflib.algorithm
|
||||
|
||||
/**
|
||||
* Interface of a diff algorithm.
|
||||
*
|
||||
* @author Tobias Warneke (t.warneke@gmx.net)
|
||||
* @param <T> type of data that is diffed.
|
||||
</T> */
|
||||
interface DiffAlgorithmI<T> {
|
||||
/**
|
||||
* Computes the changeset to patch the source list to the target list.
|
||||
*
|
||||
* @param source source data
|
||||
* @param target target data
|
||||
* @param progress progress listener
|
||||
* @return
|
||||
*/
|
||||
fun computeDiff(source: List<T>, target: List<T>, progress: DiffAlgorithmListener?): List<Change>
|
||||
|
||||
/**
|
||||
* Simple extension to compute a changeset using arrays.
|
||||
*
|
||||
* @param source
|
||||
* @param target
|
||||
* @param progress
|
||||
* @return
|
||||
*/
|
||||
// @Throws(DiffException::class)
|
||||
fun computeDiff(source: Array<T>, target: Array<T>, progress: DiffAlgorithmListener?): List<Change> {
|
||||
return computeDiff(source.toList(), target.toList(), progress)
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2018 java-diff-utils.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dev.gitlive.difflib.algorithm
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Tobias Warneke (t.warneke@gmx.net)
|
||||
*/
|
||||
interface DiffAlgorithmListener {
|
||||
fun diffStart()
|
||||
|
||||
/**
|
||||
* This is a step within the diff algorithm. Due to different implementations the value
|
||||
* is not strict incrementing to the max and is not garantee to reach the max. It could
|
||||
* stop before.
|
||||
* @param value
|
||||
* @param max
|
||||
*/
|
||||
fun diffStep(value: Int, max: Int)
|
||||
fun diffEnd()
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright 2009-2017 java-diff-utils.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dev.gitlive.difflib.algorithm.myers
|
||||
|
||||
import dev.gitlive.difflib.BiPredicate
|
||||
import dev.gitlive.difflib.algorithm.Change
|
||||
import dev.gitlive.difflib.algorithm.DiffAlgorithmFactory
|
||||
import dev.gitlive.difflib.algorithm.DiffAlgorithmI
|
||||
import dev.gitlive.difflib.algorithm.DiffAlgorithmListener
|
||||
import dev.gitlive.difflib.patch.DeltaType
|
||||
|
||||
/**
|
||||
* A clean-room implementation of Eugene Meyers greedy differencing algorithm.
|
||||
*/
|
||||
class MeyersDiff<T> : DiffAlgorithmI<T> {
|
||||
private val equalizer: BiPredicate<T, T>
|
||||
|
||||
constructor() {
|
||||
equalizer = { a: T, b: T -> a!! == b }
|
||||
}
|
||||
|
||||
constructor(equalizer: BiPredicate<T, T>) {
|
||||
this.equalizer = equalizer
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Return empty diff if get the error while procession the difference.
|
||||
*/
|
||||
override fun computeDiff(source: List<T>, target: List<T>, progress: DiffAlgorithmListener?): List<Change> {
|
||||
progress?.diffStart()
|
||||
val path = buildPath(source, target, progress)
|
||||
val result = buildRevision(path, source, target)
|
||||
progress?.diffEnd()
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the minimum diffpath that expresses de differences between the
|
||||
* original and revised sequences, according to Gene Myers differencing
|
||||
* algorithm.
|
||||
*
|
||||
* @param orig The original sequence.
|
||||
* @param rev The revised sequence.
|
||||
* @return A minimum [Path][PathNode] accross the differences graph.
|
||||
* @throws DifferentiationFailedException if a diff path could not be found.
|
||||
*/
|
||||
private fun buildPath(orig: List<T>, rev: List<T>, progress: DiffAlgorithmListener?): PathNode {
|
||||
|
||||
// these are local constants
|
||||
val N = orig.size
|
||||
val M = rev.size
|
||||
val MAX = N + M + 1
|
||||
val size = 1 + 2 * MAX
|
||||
val middle = size / 2
|
||||
val diagonal = arrayOfNulls<PathNode>(size)
|
||||
diagonal[middle + 1] = PathNode(0, -1, true, true, null)
|
||||
for (d in 0 until MAX) {
|
||||
progress?.diffStep(d, MAX)
|
||||
var k = -d
|
||||
while (k <= d) {
|
||||
val kmiddle = middle + k
|
||||
val kplus = kmiddle + 1
|
||||
val kminus = kmiddle - 1
|
||||
var prev: PathNode?
|
||||
var i: Int
|
||||
if (k == -d || k != d && diagonal[kminus]!!.i < diagonal[kplus]!!.i) {
|
||||
i = diagonal[kplus]!!.i
|
||||
prev = diagonal[kplus]
|
||||
} else {
|
||||
i = diagonal[kminus]!!.i + 1
|
||||
prev = diagonal[kminus]
|
||||
}
|
||||
diagonal[kminus] = null // no longer used
|
||||
var j = i - k
|
||||
var node = PathNode(i, j, false, false, prev)
|
||||
while (i < N && j < M && equalizer(orig[i], rev[j])) {
|
||||
i++
|
||||
j++
|
||||
}
|
||||
if (i != node.i) {
|
||||
node = PathNode(i, j, true, false, node)
|
||||
}
|
||||
diagonal[kmiddle] = node
|
||||
if (i >= N && j >= M) {
|
||||
return diagonal[kmiddle]!!
|
||||
}
|
||||
k += 2
|
||||
}
|
||||
diagonal[middle + d - 1] = null
|
||||
}
|
||||
throw IllegalStateException("could not find a diff path")
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a [Patch] from a difference path.
|
||||
*
|
||||
* @param actualPath The path.
|
||||
* @param orig The original sequence.
|
||||
* @param rev The revised sequence.
|
||||
* @return A [Patch] script corresponding to the path.
|
||||
* @throws DifferentiationFailedException if a [Patch] could not be
|
||||
* built from the given path.
|
||||
*/
|
||||
private fun buildRevision(actualPath: PathNode, orig: List<T>, rev: List<T>): List<Change> {
|
||||
var path: PathNode? = actualPath
|
||||
val changes: MutableList<Change> = ArrayList()
|
||||
if (path!!.isSnake) {
|
||||
path = path.prev
|
||||
}
|
||||
while (path?.prev != null && path.prev!!.j >= 0) {
|
||||
check(!path.isSnake) { "bad diffpath: found snake when looking for diff" }
|
||||
val i = path.i
|
||||
val j = path.j
|
||||
path = path.prev
|
||||
val ianchor = path!!.i
|
||||
val janchor = path.j
|
||||
if (ianchor == i && janchor != j) {
|
||||
changes.add(Change(DeltaType.INSERT, ianchor, i, janchor, j))
|
||||
} else if (ianchor != i && janchor == j) {
|
||||
changes.add(Change(DeltaType.DELETE, ianchor, i, janchor, j))
|
||||
} else {
|
||||
changes.add(Change(DeltaType.CHANGE, ianchor, i, janchor, j))
|
||||
}
|
||||
if (path.isSnake) {
|
||||
path = path.prev
|
||||
}
|
||||
}
|
||||
return changes
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Factory to create instances of this specific diff algorithm.
|
||||
*/
|
||||
@kotlin.jvm.JvmStatic
|
||||
fun factory(): DiffAlgorithmFactory {
|
||||
return object : DiffAlgorithmFactory {
|
||||
override fun <T> create(): DiffAlgorithmI<T> {
|
||||
return MeyersDiff()
|
||||
}
|
||||
|
||||
override fun <T> create(equalizer: BiPredicate<T, T>): DiffAlgorithmI<T> {
|
||||
return MeyersDiff(equalizer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,202 @@
|
||||
/*
|
||||
* Copyright 2021 java-diff-utils.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dev.gitlive.difflib.algorithm.myers
|
||||
|
||||
import dev.gitlive.difflib.BiPredicate
|
||||
import dev.gitlive.difflib.Consumer
|
||||
import dev.gitlive.difflib.algorithm.Change
|
||||
import dev.gitlive.difflib.algorithm.DiffAlgorithmFactory
|
||||
import dev.gitlive.difflib.algorithm.DiffAlgorithmI
|
||||
import dev.gitlive.difflib.algorithm.DiffAlgorithmListener
|
||||
import dev.gitlive.difflib.patch.DeltaType
|
||||
|
||||
/**
|
||||
*
|
||||
* @author tw
|
||||
*/
|
||||
class MeyersDiffWithLinearSpace<T> : DiffAlgorithmI<T> {
|
||||
private val equalizer: BiPredicate<T, T>
|
||||
|
||||
constructor() {
|
||||
equalizer = { a: T, b: T -> a!! == b }
|
||||
}
|
||||
|
||||
constructor(equalizer: BiPredicate<T, T>) {
|
||||
this.equalizer = equalizer
|
||||
}
|
||||
|
||||
override fun computeDiff(source: List<T>, target: List<T>, progress: DiffAlgorithmListener?): List<Change> {
|
||||
progress?.diffStart()
|
||||
val data = DiffData(source, target)
|
||||
val maxIdx = source.size + target.size
|
||||
buildScript(data, 0, source.size, 0, target.size) { idx: Int -> progress?.diffStep(idx, maxIdx) }
|
||||
progress?.diffEnd()
|
||||
return data.script
|
||||
}
|
||||
|
||||
private fun buildScript(data: DiffData, start1: Int, end1: Int, start2: Int, end2: Int, progress: Consumer<Int>?) {
|
||||
progress?.invoke((end1 - start1) / 2 + (end2 - start2) / 2)
|
||||
val middle: Snake? = getMiddleSnake(data, start1, end1, start2, end2)
|
||||
if (middle == null || middle.start == end1 && middle.diag == end1 - end2 || middle.end == start1 && middle.diag == start1 - start2) {
|
||||
var i = start1
|
||||
var j = start2
|
||||
while (i < end1 || j < end2) {
|
||||
if (i < end1 && j < end2 && equalizer(data.source[i], data.target[j])) {
|
||||
//script.append(new KeepCommand<>(left.charAt(i)));
|
||||
++i
|
||||
++j
|
||||
} else {
|
||||
//TODO: compress these commands.
|
||||
if (end1 - start1 > end2 - start2) {
|
||||
//script.append(new DeleteCommand<>(left.charAt(i)));
|
||||
if (data.script.isEmpty()
|
||||
|| data.script[data.script.size - 1].endOriginal != i || data.script[data.script.size - 1].deltaType != DeltaType.DELETE
|
||||
) {
|
||||
data.script.add(Change(DeltaType.DELETE, i, i + 1, j, j))
|
||||
} else {
|
||||
data.script[data.script.size - 1] = data.script[data.script.size - 1].withEndOriginal(i + 1)
|
||||
}
|
||||
++i
|
||||
} else {
|
||||
if (data.script.isEmpty()
|
||||
|| data.script[data.script.size - 1].endRevised != j || data.script[data.script.size - 1].deltaType != DeltaType.INSERT
|
||||
) {
|
||||
data.script.add(Change(DeltaType.INSERT, i, i, j, j + 1))
|
||||
} else {
|
||||
data.script[data.script.size - 1] = data.script[data.script.size - 1].withEndRevised(j + 1)
|
||||
}
|
||||
++j
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buildScript(data, start1, middle.start, start2, middle.start - middle.diag, progress)
|
||||
buildScript(data, middle.end, end1, middle.end - middle.diag, end2, progress)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMiddleSnake(data: DiffData, start1: Int, end1: Int, start2: Int, end2: Int): Snake? {
|
||||
val m = end1 - start1
|
||||
val n = end2 - start2
|
||||
if (m == 0 || n == 0) {
|
||||
return null
|
||||
}
|
||||
val delta = m - n
|
||||
val sum = n + m
|
||||
val offset = (if (sum % 2 == 0) sum else sum + 1) / 2
|
||||
data.vDown[1 + offset] = start1
|
||||
data.vUp[1 + offset] = end1 + 1
|
||||
for (d in 0..offset) {
|
||||
// Down
|
||||
run {
|
||||
var k = -d
|
||||
while (k <= d) {
|
||||
|
||||
// First step
|
||||
val i = k + offset
|
||||
if (k == -d || k != d && data.vDown[i - 1] < data.vDown[i + 1]) {
|
||||
data.vDown[i] = data.vDown[i + 1]
|
||||
} else {
|
||||
data.vDown[i] = data.vDown[i - 1] + 1
|
||||
}
|
||||
var x = data.vDown[i]
|
||||
var y = x - start1 + start2 - k
|
||||
while (x < end1 && y < end2 && equalizer(data.source[x], data.target[y])) {
|
||||
data.vDown[i] = ++x
|
||||
++y
|
||||
}
|
||||
// Second step
|
||||
if (delta % 2 != 0 && delta - d <= k && k <= delta + d) {
|
||||
if (data.vUp[i - delta] <= data.vDown[i]) {
|
||||
return buildSnake(data, data.vUp[i - delta], k + start1 - start2, end1, end2)
|
||||
}
|
||||
}
|
||||
k += 2
|
||||
}
|
||||
}
|
||||
|
||||
// Up
|
||||
var k = delta - d
|
||||
while (k <= delta + d) {
|
||||
|
||||
// First step
|
||||
val i = k + offset - delta
|
||||
if (k == delta - d
|
||||
|| k != delta + d && data.vUp[i + 1] <= data.vUp[i - 1]
|
||||
) {
|
||||
data.vUp[i] = data.vUp[i + 1] - 1
|
||||
} else {
|
||||
data.vUp[i] = data.vUp[i - 1]
|
||||
}
|
||||
var x = data.vUp[i] - 1
|
||||
var y = x - start1 + start2 - k
|
||||
while (x >= start1 && y >= start2 && equalizer(data.source[x], data.target[y])) {
|
||||
data.vUp[i] = x--
|
||||
y--
|
||||
}
|
||||
// Second step
|
||||
if (delta % 2 == 0 && -d <= k && k <= d) {
|
||||
if (data.vUp[i] <= data.vDown[i + delta]) {
|
||||
return buildSnake(data, data.vUp[i], k + start1 - start2, end1, end2)
|
||||
}
|
||||
}
|
||||
k += 2
|
||||
}
|
||||
}
|
||||
throw IllegalStateException("could not find a diff path")
|
||||
}
|
||||
|
||||
private fun buildSnake(data: DiffData, start: Int, diag: Int, end1: Int, end2: Int): Snake {
|
||||
var end = start
|
||||
while (end - diag < end2 && end < end1 && equalizer(data.source[end], data.target[end - diag])) {
|
||||
++end
|
||||
}
|
||||
return Snake(start, end, diag)
|
||||
}
|
||||
|
||||
private inner class DiffData(val source: List<T>, val target: List<T>) {
|
||||
val size: Int
|
||||
val vDown: IntArray
|
||||
val vUp: IntArray
|
||||
val script: MutableList<Change>
|
||||
|
||||
init {
|
||||
size = source.size + target.size + 2
|
||||
vDown = IntArray(size)
|
||||
vUp = IntArray(size)
|
||||
script = ArrayList()
|
||||
}
|
||||
}
|
||||
|
||||
private inner class Snake(val start: Int, val end: Int, val diag: Int)
|
||||
companion object {
|
||||
/**
|
||||
* Factory to create instances of this specific diff algorithm.
|
||||
*/
|
||||
@kotlin.jvm.JvmStatic
|
||||
fun factory(): DiffAlgorithmFactory {
|
||||
return object : DiffAlgorithmFactory {
|
||||
override fun <T> create(): DiffAlgorithmI<T> {
|
||||
return MeyersDiffWithLinearSpace()
|
||||
}
|
||||
|
||||
override fun <T> create(equalizer: BiPredicate<T, T>): DiffAlgorithmI<T> {
|
||||
return MeyersDiffWithLinearSpace(equalizer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Copyright 2009-2017 java-diff-utils.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dev.gitlive.difflib.algorithm.myers
|
||||
|
||||
/**
|
||||
* A node in a diffpath.
|
||||
*
|
||||
* @author [Juanco Anez](mailto:juanco@suigeneris.org)
|
||||
*/
|
||||
class PathNode(
|
||||
/**
|
||||
* Position in the original sequence.
|
||||
*/
|
||||
val i: Int,
|
||||
/**
|
||||
* Position in the revised sequence.
|
||||
*/
|
||||
val j: Int, snake: Boolean,
|
||||
/**
|
||||
* Is this a bootstrap node?
|
||||
*
|
||||
*
|
||||
* In bottstrap nodes one of the two corrdinates is less than zero.
|
||||
*
|
||||
* @return tru if this is a bootstrap node.
|
||||
*/
|
||||
val isBootstrap: Boolean, prev: PathNode?
|
||||
) {
|
||||
/**
|
||||
* The previous node in the path.
|
||||
*/
|
||||
var prev: PathNode? = null
|
||||
val isSnake: Boolean
|
||||
|
||||
/**
|
||||
* Skips sequences of [PathNodes][PathNode] until a snake or bootstrap node is found, or the end of the
|
||||
* path is reached.
|
||||
*
|
||||
* @return The next first [PathNode] or bootstrap node in the path, or `null` if none found.
|
||||
*/
|
||||
fun previousSnake(): PathNode? {
|
||||
if (isBootstrap) {
|
||||
return null
|
||||
}
|
||||
return if (!isSnake && prev != null) {
|
||||
prev!!.previousSnake()
|
||||
} else this
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
override fun toString(): String {
|
||||
val buf = StringBuilder("[")
|
||||
var node: PathNode? = this
|
||||
while (node != null) {
|
||||
buf.append("(")
|
||||
buf.append(node.i)
|
||||
buf.append(",")
|
||||
buf.append(node.j)
|
||||
buf.append(")")
|
||||
node = node.prev
|
||||
}
|
||||
buf.append("]")
|
||||
return buf.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Concatenates a new path node with an existing diffpath.
|
||||
*
|
||||
* @param i The position in the original sequence for the new node.
|
||||
* @param j The position in the revised sequence for the new node.
|
||||
* @param prev The previous node in the path.
|
||||
*/
|
||||
init {
|
||||
if (snake) {
|
||||
this.prev = prev
|
||||
} else {
|
||||
this.prev = prev?.previousSnake()
|
||||
}
|
||||
isSnake = snake
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2018 java-diff-utils.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dev.gitlive.difflib.patch
|
||||
|
||||
/**
|
||||
* Abstract delta between a source and a target.
|
||||
* @author Tobias Warneke (t.warneke@gmx.net)
|
||||
*/
|
||||
abstract class AbstractDelta<T>(val type: DeltaType, val source: Chunk<T>, val target: Chunk<T>) {
|
||||
|
||||
/**
|
||||
* Verify the chunk of this delta, to fit the target.
|
||||
* @param target
|
||||
* @throws PatchFailedException
|
||||
*/
|
||||
// @Throws(PatchFailedException::class)
|
||||
protected fun verifyChunkToFitTarget(target: List<T>): VerifyChunk {
|
||||
return source.verifyChunk(target)
|
||||
}
|
||||
|
||||
// @Throws(PatchFailedException::class)
|
||||
fun verifyAntApplyTo(target: MutableList<T>): VerifyChunk {
|
||||
val verify = verifyChunkToFitTarget(target)
|
||||
if (verify == VerifyChunk.OK) {
|
||||
applyTo(target)
|
||||
}
|
||||
return verify
|
||||
}
|
||||
|
||||
// @Throws(PatchFailedException::class)
|
||||
protected abstract fun applyTo(target: MutableList<T>)
|
||||
abstract fun restore(target: MutableList<T>)
|
||||
|
||||
/**
|
||||
* Create a new delta of the actual instance with customized chunk data.
|
||||
*/
|
||||
abstract fun withChunks(original: Chunk<T>, revised: Chunk<T>): AbstractDelta<T>
|
||||
override fun hashCode(): Int {
|
||||
return Triple(this.source, this.target, this.type).hashCode()
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) {
|
||||
return true
|
||||
}
|
||||
if (other == null) {
|
||||
return false
|
||||
}
|
||||
if (this::class != other::class) {
|
||||
return false
|
||||
}
|
||||
val other = other as AbstractDelta<*>
|
||||
if (source != other.source) {
|
||||
return false
|
||||
}
|
||||
return if (target != other.target) {
|
||||
false
|
||||
} else type == other.type
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright 2009-2017 java-diff-utils.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package dev.gitlive.difflib.patch
|
||||
|
||||
/**
|
||||
* Describes the change-delta between original and revised texts.
|
||||
*
|
||||
* @author [Dmitry Naumenko](dm.naumenko@gmail.com)
|
||||
* @param <T> The type of the compared elements in the data 'lines'.
|
||||
</T> */
|
||||
class ChangeDelta<T>(source: Chunk<T>, target: Chunk<T>) : AbstractDelta<T>(DeltaType.CHANGE, source, target) {
|
||||
// @Throws(PatchFailedException::class)
|
||||
override fun applyTo(target: MutableList<T>) {
|
||||
val position = source.position
|
||||
val size = source.size()
|
||||
for (i in 0 until size) {
|
||||
target.removeAt(position)
|
||||
}
|
||||
var i = 0
|
||||
for (line in this.target.lines) {
|
||||
target.add(position + i, line)
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
override fun restore(target: MutableList<T>) {
|
||||
val position = this.target.position
|
||||
val size = this.target.size()
|
||||
for (i in 0 until size) {
|
||||
target.removeAt(position)
|
||||
}
|
||||
var i = 0
|
||||
for (line in source.lines) {
|
||||
target.add(position + i, line)
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return ("[ChangeDelta, position: " + source.position + ", lines: "
|
||||
+ source.lines + " to " + target.lines + "]")
|
||||
}
|
||||
|
||||
override fun withChunks(original: Chunk<T>, revised: Chunk<T>): AbstractDelta<T> {
|
||||
return ChangeDelta(original, revised)
|
||||
}
|
||||
|
||||
}
|