diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..86a4630 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*.{kt,kts}] +ij_kotlin_allow_trailing_comma_on_call_site=false +ij_kotlin_allow_trailing_comma=false +ktlint_code_style = android_studio +ktlint_class_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than=2 +ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than=3 diff --git a/.github/workflows/commit-verifications.yml b/.github/workflows/commit-verifications.yml index 8d91331..24d2c71 100644 --- a/.github/workflows/commit-verifications.yml +++ b/.github/workflows/commit-verifications.yml @@ -10,13 +10,13 @@ jobs: - name: Checkout code uses: actions/checkout@v1 - - name: Set up JDK 1.8 + - name: Set up JDK 17 uses: actions/setup-java@v1 with: - java-version: 1.8 + java-version: 17 - name: Run all unit tests - run: ./gradlew test + run: ./gradlew allTests - name: Kotlin lint run: ./gradlew ktlintCheck diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1159f84..c9947c7 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,10 +13,10 @@ jobs: - name: Checkout code uses: actions/checkout@v1 - - name: Set up JDK 1.8 + - name: Set up JDK 17 uses: actions/setup-java@v1 with: - java-version: 1.8 + java-version: 17 - name: Publish JVM, JS, Linux, and Apple libraries to Maven run: ./gradlew publishAllPublicationsToSonatypeRepository @@ -38,10 +38,3 @@ jobs: SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} SIGNING_KEY: ${{ secrets.SIGNING_KEY }} - - - name: Publish Windows x86 library to Maven - run: ./gradlew publishMingwX86PublicationToSonatypeRepository - env: - SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} - SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} - SIGNING_KEY: ${{ secrets.SIGNING_KEY }} diff --git a/.gitignore b/.gitignore index 7ef5a0e..88cd381 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ /captures .externalNativeBuild .cxx +*.klib diff --git a/android-perf-test/build.gradle b/android-perf-test/build.gradle index e6c6428..66d6c88 100644 --- a/android-perf-test/build.gradle +++ b/android-perf-test/build.gradle @@ -3,8 +3,6 @@ apply plugin: "androidx.benchmark" apply plugin: 'kotlin-android' android { - compileSdkVersion 29 - compileOptions { sourceCompatibility = 1.8 targetCompatibility = 1.8 @@ -16,9 +14,11 @@ android { defaultConfig { minSdkVersion 21 - targetSdkVersion 29 + targetSdkVersion 34 + compileSdk 34 versionCode 1 versionName "1.0" + namespace "dev.andrewbailey.diff" testInstrumentationRunner 'androidx.benchmark.junit4.AndroidBenchmarkRunner' testInstrumentationRunnerArgument 'androidx.benchmark.suppressErrors', 'UNLOCKED' @@ -56,8 +56,8 @@ dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':difference') implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - testImplementation 'junit:junit:4.13' - androidTestImplementation 'androidx.test:runner:1.2.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.1' - androidTestImplementation "androidx.benchmark:benchmark-junit4:1.0.0" + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test:runner:1.6.2' + androidTestImplementation 'androidx.test.ext:junit:1.2.1' + androidTestImplementation "androidx.benchmark:benchmark-junit4:1.3.3" } diff --git a/android-perf-test/src/androidTest/AndroidManifest.xml b/android-perf-test/src/androidTest/AndroidManifest.xml index ee99eeb..82983d5 100644 --- a/android-perf-test/src/androidTest/AndroidManifest.xml +++ b/android-perf-test/src/androidTest/AndroidManifest.xml @@ -1,5 +1,5 @@ - diff --git a/android-perf-test/src/androidTest/java/dev/andrewbailey/diff/DiffBenchmarkTest.kt b/android-perf-test/src/androidTest/java/dev/andrewbailey/diff/DiffBenchmarkTest.kt index 62cfc06..1508d73 100644 --- a/android-perf-test/src/androidTest/java/dev/andrewbailey/diff/DiffBenchmarkTest.kt +++ b/android-perf-test/src/androidTest/java/dev/andrewbailey/diff/DiffBenchmarkTest.kt @@ -104,10 +104,7 @@ class DiffBenchmarkTest { } } - private fun generateList( - numberOfItems: Int, - seed: Long - ): List { + private fun generateList(numberOfItems: Int, seed: Long): List { val random = Random(seed) return List(numberOfItems) { random.nextInt() } } @@ -147,5 +144,4 @@ class DiffBenchmarkTest { return modifiedList } - } diff --git a/build.gradle b/build.gradle index 602d01d..c54e710 100644 --- a/build.gradle +++ b/build.gradle @@ -1,65 +1,42 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.72' + ext.kotlin_version = '2.0.21' repositories { google() - jcenter() + mavenCentral() maven { url "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:3.6.3' + classpath 'com.android.tools.build:gradle:8.8.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'androidx.benchmark:benchmark-gradle-plugin:1.0.0' - classpath "org.jlleitschuh.gradle:ktlint-gradle:9.1.1" - classpath "org.jetbrains.dokka:dokka-gradle-plugin:0.10.1" + classpath 'androidx.benchmark:benchmark-gradle-plugin:1.3.3' + classpath "org.jlleitschuh.gradle:ktlint-gradle:12.1.2" + classpath "org.jetbrains.dokka:org.jetbrains.dokka.gradle.plugin:2.0.0" } } allprojects { apply plugin: "org.jlleitschuh.gradle.ktlint" - apply plugin: 'org.jetbrains.dokka' group = GROUP version = VERSION_NAME repositories { google() - jcenter() + mavenCentral() } ktlint { + version = "1.5.0" android = true verbose = true - disabledRules = [ - "no-empty-class-body", - "no-blank-line-before-rbrace", - "no-wildcard-imports", + additionalEditorconfig = [ + "max_line_length": "100" ] - } - - dokka { - outputDirectory = "$rootDir/docs/1.x" - outputFormat = 'html' - multiplatform { - global { - perPackageOption { - prefix = "dev.andrewbailey.difference" - suppress = true - } - } - jvm {} - } - } - - task dokkaJavadoc(type: org.jetbrains.dokka.gradle.DokkaTask) { - outputFormat = 'html' - outputDirectory = "$buildDir/javadoc" - } + disabledRules = [ - task javadocJar(type: Jar, dependsOn: dokkaJavadoc) { - classifier = 'javadoc' - from "$buildDir/javadoc" + ] } } diff --git a/difference/build.gradle b/difference/build.gradle index da20117..8435a75 100644 --- a/difference/build.gradle +++ b/difference/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'org.jetbrains.kotlin.multiplatform' +apply plugin: 'org.jetbrains.dokka' kotlin { jvm { @@ -33,9 +34,9 @@ kotlin { artifactId = 'difference-macos-x64' } } - mingwX86 { + macosArm64 { mavenPublication { - artifactId = 'difference-mingw-x86' + artifactId = 'difference-macos-x64' } } mingwX64 { @@ -78,10 +79,17 @@ kotlin { implementation kotlin('test-js') } } - all { - languageSettings.enableLanguageFeature("InlineClasses") - } } } +dokkaHtml { + outputDirectory = file("$rootDir/docs/1.x") +} + +tasks.register('javadocJar', Jar) { + dependsOn dokkaHtml + archiveClassifier = 'javadoc' + from file("$rootDir/docs/1.x") +} + apply from: "$rootDir/gradle/publish.gradle" diff --git a/difference/src/commonMain/kotlin/dev/andrewbailey/diff/DiffGenerator.kt b/difference/src/commonMain/kotlin/dev/andrewbailey/diff/DiffGenerator.kt index d6af9d8..43b97ea 100644 --- a/difference/src/commonMain/kotlin/dev/andrewbailey/diff/DiffGenerator.kt +++ b/difference/src/commonMain/kotlin/dev/andrewbailey/diff/DiffGenerator.kt @@ -1,8 +1,15 @@ package dev.andrewbailey.diff -import dev.andrewbailey.diff.DiffOperation.* +import dev.andrewbailey.diff.DiffOperation.Add +import dev.andrewbailey.diff.DiffOperation.AddAll +import dev.andrewbailey.diff.DiffOperation.Move +import dev.andrewbailey.diff.DiffOperation.MoveRange +import dev.andrewbailey.diff.DiffOperation.Remove +import dev.andrewbailey.diff.DiffOperation.RemoveRange import dev.andrewbailey.diff.impl.MyersDiffAlgorithm -import dev.andrewbailey.diff.impl.MyersDiffOperation.* +import dev.andrewbailey.diff.impl.MyersDiffOperation.Delete +import dev.andrewbailey.diff.impl.MyersDiffOperation.Insert +import dev.andrewbailey.diff.impl.MyersDiffOperation.Skip internal object DiffGenerator { @@ -83,7 +90,8 @@ internal object DiffGenerator { var endIndexDifference = 0 while (indexOfOppositeAction < operations.size && - !canBeReducedToMove(operation, operations[indexOfOppositeAction])) { + !canBeReducedToMove(operation, operations[indexOfOppositeAction]) + ) { val rejectedOperation = operations[indexOfOppositeAction] if (rejectedOperation is Add) { endIndexDifference++ @@ -123,12 +131,10 @@ internal object DiffGenerator { private fun canBeReducedToMove( operation1: DiffOperation, operation2: DiffOperation - ): Boolean { - return when (operation1) { - is Add -> operation2 is Remove && operation1.item == operation2.item - is Remove -> operation2 is Add && operation1.item == operation2.item - else -> false - } + ): Boolean = when (operation1) { + is Add -> operation2 is Remove && operation1.item == operation2.item + is Remove -> operation2 is Add && operation1.item == operation2.item + else -> false } private fun reduceSequences( @@ -142,7 +148,8 @@ internal object DiffGenerator { var sequenceEndIndex = index + 1 var sequenceLength = 1 while (sequenceEndIndex < operations.size && - operationToReduce.canBeCombinedWith(operations[sequenceEndIndex], sequenceLength)) { + operationToReduce.canBeCombinedWith(operations[sequenceEndIndex], sequenceLength) + ) { sequenceEndIndex++ sequenceLength++ } @@ -167,63 +174,64 @@ internal object DiffGenerator { val sequenceLength = sequenceEndIndex - sequenceStartIndex return if (sequenceLength == 1) { operations[sequenceStartIndex] - } else when (val startOperation = operations[sequenceStartIndex]) { - is Remove -> { - RemoveRange( - startIndex = startOperation.index, - endIndex = startOperation.index + sequenceLength - ) - } - is Add -> { - AddAll( - index = startOperation.index, - items = operations.subList(sequenceStartIndex, sequenceEndIndex) - .asSequence() - .map { operation -> - require(operation is Add) { - "Cannot reduce $operation as part of an insert sequence because " + - "it is not an add action." + } else { + when (val startOperation = operations[sequenceStartIndex]) { + is Remove -> { + RemoveRange( + startIndex = startOperation.index, + endIndex = startOperation.index + sequenceLength + ) + } + is Add -> { + AddAll( + index = startOperation.index, + items = operations.subList(sequenceStartIndex, sequenceEndIndex) + .asSequence() + .map { operation -> + require(operation is Add) { + "Cannot reduce $operation as part of an insert sequence " + + "because it is not an add action." + } + + operation.item } - - operation.item - } - .toList() - ) - } - is Move -> { - MoveRange( - fromIndex = startOperation.fromIndex, - toIndex = startOperation.toIndex, - itemCount = sequenceLength + .toList() + ) + } + is Move -> { + MoveRange( + fromIndex = startOperation.fromIndex, + toIndex = startOperation.toIndex, + itemCount = sequenceLength + ) + } + else -> throw IllegalArgumentException( + "Cannot reduce sequence starting with $startOperation" ) } - else -> throw IllegalArgumentException( - "Cannot reduce sequence starting with $startOperation" - ) } } private fun DiffOperation.canBeCombinedWith( otherOperation: DiffOperation, offset: Int - ): Boolean { - return when (this) { - is Remove -> otherOperation is Remove && index == otherOperation.index - is Add -> otherOperation is Add && index + offset == otherOperation.index - is Move -> otherOperation is Move && when { - toIndex < fromIndex -> { - // Move backwards case - toIndex + offset == otherOperation.toIndex && + ): Boolean = when (this) { + is Remove -> otherOperation is Remove && index == otherOperation.index + is Add -> otherOperation is Add && index + offset == otherOperation.index + is Move -> + otherOperation is Move && + when { + toIndex < fromIndex -> { + // Move backwards case + toIndex + offset == otherOperation.toIndex && fromIndex + offset == otherOperation.fromIndex - } - else -> { - // Move forwards case - toIndex == otherOperation.toIndex && + } + else -> { + // Move forwards case + toIndex == otherOperation.toIndex && fromIndex == otherOperation.fromIndex + } } - } - else -> false - } + else -> false } - } diff --git a/difference/src/commonMain/kotlin/dev/andrewbailey/diff/DiffOperation.kt b/difference/src/commonMain/kotlin/dev/andrewbailey/diff/DiffOperation.kt index 889e5a2..2a5dcd2 100644 --- a/difference/src/commonMain/kotlin/dev/andrewbailey/diff/DiffOperation.kt +++ b/difference/src/commonMain/kotlin/dev/andrewbailey/diff/DiffOperation.kt @@ -32,5 +32,4 @@ sealed class DiffOperation { val toIndex: Int, val itemCount: Int ) : DiffOperation() - } diff --git a/difference/src/commonMain/kotlin/dev/andrewbailey/diff/DiffResult.kt b/difference/src/commonMain/kotlin/dev/andrewbailey/diff/DiffResult.kt index 2982fe1..ba1316b 100644 --- a/difference/src/commonMain/kotlin/dev/andrewbailey/diff/DiffResult.kt +++ b/difference/src/commonMain/kotlin/dev/andrewbailey/diff/DiffResult.kt @@ -1,8 +1,6 @@ package dev.andrewbailey.diff -class DiffResult internal constructor( - val operations: List> -) { +class DiffResult internal constructor(val operations: List>) { inline fun applyDiff( crossinline remove: (index: Int) -> Unit, @@ -72,13 +70,9 @@ class DiffResult internal constructor( } } - override fun equals(other: Any?) = - other is DiffResult<*> && other.operations == operations + override fun equals(other: Any?) = other is DiffResult<*> && other.operations == operations - override fun hashCode() = - operations.hashCode() - - override fun toString() = - "DiffResult(operations = $operations)" + override fun hashCode() = operations.hashCode() + override fun toString() = "DiffResult(operations = $operations)" } diff --git a/difference/src/commonMain/kotlin/dev/andrewbailey/diff/impl/CircularIntArray.kt b/difference/src/commonMain/kotlin/dev/andrewbailey/diff/impl/CircularIntArray.kt index 7ac358d..67d215d 100644 --- a/difference/src/commonMain/kotlin/dev/andrewbailey/diff/impl/CircularIntArray.kt +++ b/difference/src/commonMain/kotlin/dev/andrewbailey/diff/impl/CircularIntArray.kt @@ -1,14 +1,13 @@ package dev.andrewbailey.diff.impl -internal inline class CircularIntArray( - val array: IntArray -) { +import kotlin.jvm.JvmInline + +@JvmInline +internal value class CircularIntArray(private val array: IntArray) { constructor(size: Int) : this(IntArray(size)) - operator fun get(index: Int): Int { - return array[toInternalIndex(index)] - } + operator fun get(index: Int): Int = array[toInternalIndex(index)] operator fun set(index: Int, value: Int) { array[toInternalIndex(index)] = value @@ -22,5 +21,4 @@ internal inline class CircularIntArray( moddedIndex } } - } diff --git a/difference/src/commonMain/kotlin/dev/andrewbailey/diff/impl/MyersDiffAlgorithm.kt b/difference/src/commonMain/kotlin/dev/andrewbailey/diff/impl/MyersDiffAlgorithm.kt index 2799d29..1f55a48 100644 --- a/difference/src/commonMain/kotlin/dev/andrewbailey/diff/impl/MyersDiffAlgorithm.kt +++ b/difference/src/commonMain/kotlin/dev/andrewbailey/diff/impl/MyersDiffAlgorithm.kt @@ -1,6 +1,8 @@ package dev.andrewbailey.diff.impl -import dev.andrewbailey.diff.impl.MyersDiffOperation.* +import dev.andrewbailey.diff.impl.MyersDiffOperation.Delete +import dev.andrewbailey.diff.impl.MyersDiffOperation.Insert +import dev.andrewbailey.diff.impl.MyersDiffOperation.Skip import kotlin.math.ceil /** @@ -36,17 +38,15 @@ internal class MyersDiffAlgorithm( private val updated: List ) { - fun generateDiff(): Sequence> { - return walkSnakes() - .asSequence() - .map { (x1, y1, x2, y2) -> - when { - x1 == x2 -> Insert(value = updated[y1]) - y1 == y2 -> Delete - else -> Skip - } + fun generateDiff(): Sequence> = walkSnakes() + .asSequence() + .map { (x1, y1, x2, y2) -> + when { + x1 == x2 -> Insert(value = updated[y1]) + y1 == y2 -> Delete + else -> Skip } - } + } private fun walkSnakes(): List { val path = findPath() @@ -112,34 +112,34 @@ internal class MyersDiffAlgorithm( snakes += snake val (start, finish) = snake - stack.push(region.copy( - right = start.x, - bottom = start.y - )) + stack.push( + region.copy( + right = start.x, + bottom = start.y + ) + ) - stack.push(region.copy( - left = finish.x, - top = finish.y - )) + stack.push( + region.copy( + left = finish.x, + top = finish.y + ) + ) } } snakes.sortWith(object : Comparator { - override fun compare(a: Snake, b: Snake): Int { - return if (a.start.x == b.start.x) { - a.start.y - b.start.y - } else { - a.start.x - b.start.x - } + override fun compare(a: Snake, b: Snake): Int = if (a.start.x == b.start.x) { + a.start.y - b.start.y + } else { + a.start.x - b.start.x } }) return snakes } - private fun midpoint( - region: Region - ): Snake? { + private fun midpoint(region: Region): Snake? { if (region.size == 0) { return null } @@ -185,8 +185,10 @@ internal class MyersDiffAlgorithm( var endY = region.top + (endX - region.left) - k val startY = if (depth == 0 || endX != startX) endY else endY - 1 - while (endX < region.right && endY < region.bottom && - original[endX] == updated[endY]) { + while (endX < region.right && + endY < region.bottom && + original[endX] == updated[endY] + ) { endX++ endY++ } @@ -232,8 +234,10 @@ internal class MyersDiffAlgorithm( var startX = region.left + (startY - region.top) + k val endX = if (depth == 0 || startY != endY) startX else startX + 1 - while (startX > region.left && startY > region.top && - original[startX - 1] == updated[startY - 1]) { + while (startX > region.left && + startY > region.top && + original[startX - 1] == updated[startY - 1] + ) { startX-- startY-- } @@ -252,5 +256,4 @@ internal class MyersDiffAlgorithm( return null } - } diff --git a/difference/src/commonMain/kotlin/dev/andrewbailey/diff/impl/MyersDiffOperation.kt b/difference/src/commonMain/kotlin/dev/andrewbailey/diff/impl/MyersDiffOperation.kt index 41e1a4f..121d885 100644 --- a/difference/src/commonMain/kotlin/dev/andrewbailey/diff/impl/MyersDiffOperation.kt +++ b/difference/src/commonMain/kotlin/dev/andrewbailey/diff/impl/MyersDiffOperation.kt @@ -2,12 +2,9 @@ package dev.andrewbailey.diff.impl internal sealed class MyersDiffOperation { - data class Insert( - val value: T - ) : MyersDiffOperation() + data class Insert(val value: T) : MyersDiffOperation() - object Delete : MyersDiffOperation() - - object Skip : MyersDiffOperation() + data object Delete : MyersDiffOperation() + data object Skip : MyersDiffOperation() } diff --git a/difference/src/commonTest/kotlin/dev/andrewbailey/diff/DiffGeneratorTest.kt b/difference/src/commonTest/kotlin/dev/andrewbailey/diff/DiffGeneratorTest.kt index 924083a..7d544df 100644 --- a/difference/src/commonTest/kotlin/dev/andrewbailey/diff/DiffGeneratorTest.kt +++ b/difference/src/commonTest/kotlin/dev/andrewbailey/diff/DiffGeneratorTest.kt @@ -1,13 +1,18 @@ package dev.andrewbailey.diff -import dev.andrewbailey.diff.DiffOperation.* +import dev.andrewbailey.diff.DiffOperation.Add +import dev.andrewbailey.diff.DiffOperation.AddAll +import dev.andrewbailey.diff.DiffOperation.Move +import dev.andrewbailey.diff.DiffOperation.MoveRange +import dev.andrewbailey.diff.DiffOperation.Remove +import dev.andrewbailey.diff.DiffOperation.RemoveRange import kotlin.test.Test import kotlin.test.assertEquals class DiffGeneratorTest { @Test - fun `generateDiff with empty input returns empty result`() { + fun generateDiff_withEmptyInput_returnsEmptyResult() { val original = emptyList() val updated = emptyList() @@ -27,7 +32,7 @@ class DiffGeneratorTest { } @Test - fun `generateDiff with empty start returns additions`() { + fun generateDiff_withEmptyStart_returnsAdditions() { val original = emptyList() val updated = listOf("A", "B", "C") @@ -35,12 +40,14 @@ class DiffGeneratorTest { assertEquals( message = "The returned diff did not match the expected value.", - expected = DiffResult(listOf( - AddAll( - index = 0, - items = listOf("A", "B", "C") + expected = DiffResult( + listOf( + AddAll( + index = 0, + items = listOf("A", "B", "C") + ) ) - )), + ), actual = diff ) @@ -52,7 +59,7 @@ class DiffGeneratorTest { } @Test - fun `generateDiff with empty end returns deletions`() { + fun generateDiff_withEmptyEnd_returnsDeletions() { val original = listOf("A", "B", "C") val updated = emptyList() @@ -60,12 +67,14 @@ class DiffGeneratorTest { assertEquals( message = "The returned diff did not match the expected value.", - expected = DiffResult(listOf( - RemoveRange( - startIndex = 0, - endIndex = 3 + expected = DiffResult( + listOf( + RemoveRange( + startIndex = 0, + endIndex = 3 + ) ) - )), + ), actual = diff ) @@ -77,7 +86,7 @@ class DiffGeneratorTest { } @Test - fun `generateDiff with same start and end returns empty diff`() { + fun generateDiff_withSameStartAndEnd_returnsEmptyDiff() { val original = listOf("A", "B", "C") val updated = listOf("A", "B", "C") @@ -97,7 +106,7 @@ class DiffGeneratorTest { } @Test - fun `generateDiff without moves calculates complex diff`() { + fun generateDiff_withoutMoves_calculatesComplexDiff() { val original = "ABCDEFGHJKLPQR".toList() val updated = "BCAGHIJLMNOPQR".toList() @@ -109,14 +118,16 @@ class DiffGeneratorTest { assertEquals( message = "The returned diff did not match the expected value.", - expected = DiffResult(listOf( - Remove(index = 0, item = 'A'), - RemoveRange(startIndex = 2, endIndex = 5), - Add(index = 2, item = 'A'), - Add(index = 5, item = 'I'), - Remove(index = 7, item = 'K'), - AddAll(index = 8, items = "MNO".toList()) - )), + expected = DiffResult( + listOf( + Remove(index = 0, item = 'A'), + RemoveRange(startIndex = 2, endIndex = 5), + Add(index = 2, item = 'A'), + Add(index = 5, item = 'I'), + Remove(index = 7, item = 'K'), + AddAll(index = 8, items = "MNO".toList()) + ) + ), actual = diff ) @@ -128,7 +139,7 @@ class DiffGeneratorTest { } @Test - fun `generateDiff detects forwards and backwards movements`() { + fun generateDiff_detectsForwardsAndBackwardsMovements() { val original = "CADEFB".toList() val updated = "ABCDEF".toList() @@ -140,16 +151,18 @@ class DiffGeneratorTest { assertEquals( message = "The returned diff did not match the expected value.", - expected = DiffResult(listOf( - Move( - fromIndex = 0, - toIndex = 2 - ), - Move( - fromIndex = 5, - toIndex = 1 + expected = DiffResult( + listOf( + Move( + fromIndex = 0, + toIndex = 2 + ), + Move( + fromIndex = 5, + toIndex = 1 + ) ) - )), + ), actual = diff ) @@ -161,7 +174,7 @@ class DiffGeneratorTest { } @Test - fun `generateDiff detects move forwards sequences`() { + fun generateDiff_detectsMoveForwardsSequences() { val original = "ABCDEFGHIJKL".toList() val updated = "ABCGHIJKLDEF".toList() @@ -173,13 +186,15 @@ class DiffGeneratorTest { assertEquals( message = "The returned diff did not match the expected value.", - expected = DiffResult(listOf( - MoveRange( - fromIndex = 3, - toIndex = 12, - itemCount = 3 + expected = DiffResult( + listOf( + MoveRange( + fromIndex = 3, + toIndex = 12, + itemCount = 3 + ) ) - )), + ), actual = diff ) @@ -191,7 +206,7 @@ class DiffGeneratorTest { } @Test - fun `generateDiff detects move backwards sequences`() { + fun generateDiff_DetectsMoveBackwardsSequences() { val original = "ABCDEFGHIJKL".toList() val updated = "HIJABCDEFGKL".toList() @@ -203,13 +218,15 @@ class DiffGeneratorTest { assertEquals( message = "The returned diff did not match the expected value.", - expected = DiffResult(listOf( - MoveRange( - fromIndex = 7, - toIndex = 0, - itemCount = 3 + expected = DiffResult( + listOf( + MoveRange( + fromIndex = 7, + toIndex = 0, + itemCount = 3 + ) ) - )), + ), actual = diff ) @@ -221,7 +238,7 @@ class DiffGeneratorTest { } @Test - fun `generateDiff detects adjacent moves to different destinations`() { + fun generateDiff_DetectsAdjacentMovesToDifferentDestinations() { val original = "ABCDEFGHIJKL".toList() val updated = "DABCGHEIJKLF".toList() @@ -233,20 +250,22 @@ class DiffGeneratorTest { assertEquals( message = "The returned diff did not match the expected value.", - expected = DiffResult(listOf( - Move( - fromIndex = 3, - toIndex = 0 - ), - Move( - fromIndex = 4, - toIndex = 8 - ), - Move( - fromIndex = 4, - toIndex = 12 + expected = DiffResult( + listOf( + Move( + fromIndex = 3, + toIndex = 0 + ), + Move( + fromIndex = 4, + toIndex = 8 + ), + Move( + fromIndex = 4, + toIndex = 12 + ) ) - )), + ), actual = diff ) @@ -258,7 +277,7 @@ class DiffGeneratorTest { } @Test - fun `generateDiff with moves excludes opposite operations from merging`() { + fun generateDiff_withMoves_excludesOppositeOperationsFromMerging() { val original = listOf(3, 2, 3, 0, 0, 3, 1, 0, 1, 2) val updated = listOf(1, 3, 2, 0, 12, 0, 15, 0, 1, 2, 3) @@ -270,28 +289,30 @@ class DiffGeneratorTest { assertEquals( message = "The returned diff did not match the expected value.", - expected = DiffResult(listOf( - Move( - fromIndex = 6, - toIndex = 0 - ), - Move( - fromIndex = 3, - toIndex = 10 - ), - Add( - index = 4, - item = 12 - ), - Remove( - index = 6, - item = 3 - ), - Add( - index = 6, - item = 15 + expected = DiffResult( + listOf( + Move( + fromIndex = 6, + toIndex = 0 + ), + Move( + fromIndex = 3, + toIndex = 10 + ), + Add( + index = 4, + item = 12 + ), + Remove( + index = 6, + item = 3 + ), + Add( + index = 6, + item = 15 + ) ) - )), + ), actual = diff ) @@ -302,8 +323,8 @@ class DiffGeneratorTest { ) } - private fun applyDiff(original: List, diff: DiffResult): List { - return original.toMutableList().apply { + private fun applyDiff(original: List, diff: DiffResult): List = + original.toMutableList().apply { diff.applyDiff( remove = { index -> removeAt(index) }, insert = { item, index -> add(index, item) }, @@ -319,6 +340,4 @@ class DiffGeneratorTest { } ) } - } - } diff --git a/difference/src/commonTest/kotlin/dev/andrewbailey/diff/impl/MyersDiffAlgorithmTest.kt b/difference/src/commonTest/kotlin/dev/andrewbailey/diff/impl/MyersDiffAlgorithmTest.kt index eb9f1f3..0555bc3 100644 --- a/difference/src/commonTest/kotlin/dev/andrewbailey/diff/impl/MyersDiffAlgorithmTest.kt +++ b/difference/src/commonTest/kotlin/dev/andrewbailey/diff/impl/MyersDiffAlgorithmTest.kt @@ -1,13 +1,15 @@ package dev.andrewbailey.diff.impl -import dev.andrewbailey.diff.impl.MyersDiffOperation.* +import dev.andrewbailey.diff.impl.MyersDiffOperation.Delete +import dev.andrewbailey.diff.impl.MyersDiffOperation.Insert +import dev.andrewbailey.diff.impl.MyersDiffOperation.Skip import kotlin.test.Test import kotlin.test.assertEquals class MyersDiffAlgorithmTest { @Test - fun `generateDiff with empty inputs returns empty sequence`() { + fun generateDiff_withEmptyInputs_returnsEmptySequence() { assertEquals( expected = emptyList(), actual = MyersDiffAlgorithm( @@ -18,7 +20,7 @@ class MyersDiffAlgorithmTest { } @Test - fun `generateDiff with empty start returns additions`() { + fun generateDiff_withEmptyStart_returnsAdditions() { assertEquals( expected = listOf(Insert("A"), Insert("B"), Insert("C")), actual = MyersDiffAlgorithm( @@ -29,7 +31,7 @@ class MyersDiffAlgorithmTest { } @Test - fun `generateDiff with empty end returns deletions`() { + fun generateDiff_withEmptyEnd_returnsDeletions() { assertEquals( expected = listOf(Delete, Delete, Delete), actual = MyersDiffAlgorithm( @@ -40,7 +42,7 @@ class MyersDiffAlgorithmTest { } @Test - fun `generateDiff with same start and end returns skips`() { + fun generateDiff_withSameStartAndEnd_returnsSkips() { assertEquals( expected = listOf(Skip, Skip, Skip, Skip), actual = MyersDiffAlgorithm( @@ -51,7 +53,7 @@ class MyersDiffAlgorithmTest { } @Test - fun `generateDiff with simple example`() { + fun generateDiff_withSimpleExample() { val original = "ABCABAC".toList() val updated = "CBABAC".toList() @@ -73,7 +75,7 @@ class MyersDiffAlgorithmTest { } @Test - fun `generateDiff with DNA example`() { + fun generateDiff_withDnaExample() { val original = "tgtcgctctcaagatggcgtcttattacgaaaggagccagtccgggttgc".toList() val updated = "ggctggggttttcgcacggcgctccctccgcggttgtatctcaggcgaca".toList() @@ -93,22 +95,26 @@ class MyersDiffAlgorithmTest { } @Test - fun `generateDiff with lorem ipsum example`() { - val original = ("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod " + - "tempor incididunt ut labore et dolore magna aliqua. Quis auctor elit sed vulputate " + - "mi sit amet mauris commodo. Nec dui nunc mattis enim ut tellus elementum. Ultricies " + - "integer quis auctor elit sed vulputate mi sit amet. Ullamcorper velit sed " + - "ullamcorper morbi tincidunt.").toLowerCase().split(" ") - val updated = ("Malesuada fames ac turpis egestas. Varius sit amet mattis vulputate " + - "enim. Nisl nisi scelerisque eu ultrices vitae auctor eu augue. Sit amet volutpat " + - "consequat mauris nunc congue nisi vitae. Egestas purus viverra accumsan in nisl " + - "nisi scelerisque eu. Lobortis elementum nibh tellus molestie. Nulla at volutpat " + - "diam ut venenatis tellus in metus. Ac turpis egestas sed tempus urna et pharetra " + - "pharetra massa. Etiam sit amet nisl purus in mollis. Vivamus arcu felis bibendum ut " + - "tristique et egestas quis. Vestibulum lorem sed risus ultricies tristique nulla " + - "aliquet. Nunc scelerisque viverra mauris in aliquam. Facilisis magna etiam tempor " + - "orci eu lobortis elementum nibh. Purus faucibus ornare suspendisse sed nisi. Dui " + - "accumsan sit amet nulla.").toLowerCase().split(" ") + fun generateDiff_withLoremIpsumExample() { + val original = ( + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod " + + "tempor incididunt ut labore et dolore magna aliqua. Quis auctor elit sed " + + "vulputate mi sit amet mauris commodo. Nec dui nunc mattis enim ut tellus " + + "elementum. Ultricies integer quis auctor elit sed vulputate mi sit amet. " + + "Ullamcorper velit sed ullamcorper morbi tincidunt." + ).lowercase().split(" ") + val updated = ( + "Malesuada fames ac turpis egestas. Varius sit amet mattis vulputate enim. Nisl " + + "nisi scelerisque eu ultrices vitae auctor eu augue. Sit amet volutpat consequat " + + "mauris nunc congue nisi vitae. Egestas purus viverra accumsan in nisl nisi " + + "scelerisque eu. Lobortis elementum nibh tellus molestie. Nulla at volutpat diam " + + "ut venenatis tellus in metus. Ac turpis egestas sed tempus urna et pharetra " + + "pharetra massa. Etiam sit amet nisl purus in mollis. Vivamus arcu felis " + + "bibendum ut tristique et egestas quis. Vestibulum lorem sed risus ultricies " + + "tristique nulla aliquet. Nunc scelerisque viverra mauris in aliquam. Facilisis " + + "magna etiam tempor orci eu lobortis elementum nibh. Purus faucibus ornare " + + "suspendisse sed nisi. Dui accumsan sit amet nulla." + ).lowercase().split(" ") val diff = MyersDiffAlgorithm(original, updated).generateDiff() @@ -125,8 +131,8 @@ class MyersDiffAlgorithmTest { ) } - private fun applyDiff(original: List, diff: Sequence>): List { - return original.toMutableList().apply { + private fun applyDiff(original: List, diff: Sequence>): List = + original.toMutableList().apply { var index = 0 diff.forEach { operation -> when (operation) { @@ -136,6 +142,4 @@ class MyersDiffAlgorithmTest { } } } - } - } diff --git a/difference/src/jvmMain/kotlin/dev/andrewbailey/diff/DiffReceiver.kt b/difference/src/jvmMain/kotlin/dev/andrewbailey/diff/DiffReceiver.kt index 12a886a..3829537 100644 --- a/difference/src/jvmMain/kotlin/dev/andrewbailey/diff/DiffReceiver.kt +++ b/difference/src/jvmMain/kotlin/dev/andrewbailey/diff/DiffReceiver.kt @@ -1,14 +1,19 @@ package dev.andrewbailey.diff -import dev.andrewbailey.diff.DiffOperation.* +import dev.andrewbailey.diff.DiffOperation.Add +import dev.andrewbailey.diff.DiffOperation.AddAll +import dev.andrewbailey.diff.DiffOperation.Move +import dev.andrewbailey.diff.DiffOperation.MoveRange +import dev.andrewbailey.diff.DiffOperation.Remove +import dev.andrewbailey.diff.DiffOperation.RemoveRange /** - * This class serves as a convenience class for Java users who may find it tedious to call [DiffResult.applyDiff], - * since Java users have to rely on Kotlin's `FunctionN` interfaces and don't have access to Kotlin's named arguments - * and default arguments. + * This class serves as a convenience class for Java users who may find it tedious to call + * [DiffResult.applyDiff], since Java users have to rely on Kotlin's `FunctionN` interfaces and + * don't have access to Kotlin's named arguments and default arguments. * - * If you're using Difference directly in Kotlin, then there's no reason for you to use this class since the - * [DiffResult.applyDiff] is a more idiomatic way to use a difference result. + * If you're using Difference directly in Kotlin, then there's no reason for you to use this class + * since the [DiffResult.applyDiff] is a more idiomatic way to use a difference result. */ abstract class DiffReceiver { @@ -53,14 +58,17 @@ abstract class DiffReceiver { } } - open fun move(oldIndex: Int, newIndex: Int) { - throw UnsupportedOperationException("The received diff included move operations, but " + - "this receiver does not support moving elements. You should either disable " + - "movement detection when generating the diff, or override the " + - "`DiffReceiver.move()` function.") - } + open fun move(oldIndex: Int, newIndex: Int): Unit = throw UnsupportedOperationException( + "The received diff included move operations, but this receiver does not support moving " + + "elements. You should either disable movement detection when generating the " + + "diff, or override the `DiffReceiver.move()` function." + ) - open fun moveRange(oldIndex: Int, newIndex: Int, count: Int) { + open fun moveRange( + oldIndex: Int, + newIndex: Int, + count: Int + ) { when { newIndex < oldIndex -> { (0 until count).forEach { item -> @@ -74,5 +82,4 @@ abstract class DiffReceiver { } } } - } diff --git a/difference/src/jvmTest/kotlin/dev/andrewbailey/diff/DiffReceiverTest.kt b/difference/src/jvmTest/kotlin/dev/andrewbailey/diff/DiffReceiverTest.kt index 1c39782..62e41d7 100644 --- a/difference/src/jvmTest/kotlin/dev/andrewbailey/diff/DiffReceiverTest.kt +++ b/difference/src/jvmTest/kotlin/dev/andrewbailey/diff/DiffReceiverTest.kt @@ -48,9 +48,7 @@ class DiffReceiverTest { return List(500) { random.nextInt().toString() } } - private fun generateModifiedList( - originalData: List - ): List { + private fun generateModifiedList(originalData: List): List { val random = Random(3260128955430943624) val modifiedList = originalData.toMutableList() @@ -81,5 +79,4 @@ class DiffReceiverTest { return modifiedList } - } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf..62d4c05 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a4f0001..15cbd6d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Tue Jan 21 21:18:25 EST 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.4.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 83f2acf..fbd7c51 100755 --- a/gradlew +++ b/gradlew @@ -82,6 +82,7 @@ esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then @@ -129,6 +130,7 @@ fi if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath @@ -154,19 +156,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then else eval `echo args$i`="\"$arg\"" fi - i=$((i+1)) + i=`expr $i + 1` done case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi @@ -175,14 +177,9 @@ save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } -APP_ARGS=$(save "$@") +APP_ARGS=`save "$@"` # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" -fi - exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 24467a1..a9f778a 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @@ -81,6 +84,7 @@ set CMD_LINE_ARGS=%* set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%