From 82f72126793b07dca130bc9edab72734d8141e4f Mon Sep 17 00:00:00 2001 From: Andrew Bailey Date: Tue, 21 Jan 2025 22:08:16 -0500 Subject: [PATCH 1/2] Update all dependencies --- .github/workflows/commit-verifications.yml | 6 +-- .github/workflows/publish.yml | 11 +----- android-perf-test/build.gradle | 14 +++---- .../src/androidTest/AndroidManifest.xml | 2 +- build.gradle | 37 +++--------------- difference/build.gradle | 18 ++++++--- .../andrewbailey/diff/DiffGeneratorTest.kt | 20 +++++----- .../diff/impl/MyersDiffAlgorithmTest.kt | 14 +++---- gradle/wrapper/gradle-wrapper.jar | Bin 55616 -> 58910 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- gradlew | 31 +++++++-------- gradlew.bat | 4 ++ 12 files changed, 69 insertions(+), 91 deletions(-) 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/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/build.gradle b/build.gradle index 602d01d..5388a6d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,32 +1,31 @@ // 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 'androidx.benchmark:benchmark-gradle-plugin:1.3.3' classpath "org.jlleitschuh.gradle:ktlint-gradle:9.1.1" - classpath "org.jetbrains.dokka:dokka-gradle-plugin:0.10.1" + 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 { @@ -38,28 +37,4 @@ allprojects { "no-wildcard-imports", ] } - - 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" - } - - 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/commonTest/kotlin/dev/andrewbailey/diff/DiffGeneratorTest.kt b/difference/src/commonTest/kotlin/dev/andrewbailey/diff/DiffGeneratorTest.kt index 924083a..5ff5bd3 100644 --- a/difference/src/commonTest/kotlin/dev/andrewbailey/diff/DiffGeneratorTest.kt +++ b/difference/src/commonTest/kotlin/dev/andrewbailey/diff/DiffGeneratorTest.kt @@ -7,7 +7,7 @@ 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 +27,7 @@ class DiffGeneratorTest { } @Test - fun `generateDiff with empty start returns additions`() { + fun generateDiff_withEmptyStart_returnsAdditions() { val original = emptyList() val updated = listOf("A", "B", "C") @@ -52,7 +52,7 @@ class DiffGeneratorTest { } @Test - fun `generateDiff with empty end returns deletions`() { + fun generateDiff_withEmptyEnd_returnsDeletions() { val original = listOf("A", "B", "C") val updated = emptyList() @@ -77,7 +77,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 +97,7 @@ class DiffGeneratorTest { } @Test - fun `generateDiff without moves calculates complex diff`() { + fun generateDiff_withoutMoves_calculatesComplexDiff() { val original = "ABCDEFGHJKLPQR".toList() val updated = "BCAGHIJLMNOPQR".toList() @@ -128,7 +128,7 @@ class DiffGeneratorTest { } @Test - fun `generateDiff detects forwards and backwards movements`() { + fun generateDiff_detectsForwardsAndBackwardsMovements() { val original = "CADEFB".toList() val updated = "ABCDEF".toList() @@ -161,7 +161,7 @@ class DiffGeneratorTest { } @Test - fun `generateDiff detects move forwards sequences`() { + fun generateDiff_detectsMoveForwardsSequences() { val original = "ABCDEFGHIJKL".toList() val updated = "ABCGHIJKLDEF".toList() @@ -191,7 +191,7 @@ class DiffGeneratorTest { } @Test - fun `generateDiff detects move backwards sequences`() { + fun generateDiff_DetectsMoveBackwardsSequences() { val original = "ABCDEFGHIJKL".toList() val updated = "HIJABCDEFGKL".toList() @@ -221,7 +221,7 @@ class DiffGeneratorTest { } @Test - fun `generateDiff detects adjacent moves to different destinations`() { + fun generateDiff_DetectsAdjacentMovesToDifferentDestinations() { val original = "ABCDEFGHIJKL".toList() val updated = "DABCGHEIJKLF".toList() @@ -258,7 +258,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) 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..f718eac 100644 --- a/difference/src/commonTest/kotlin/dev/andrewbailey/diff/impl/MyersDiffAlgorithmTest.kt +++ b/difference/src/commonTest/kotlin/dev/andrewbailey/diff/impl/MyersDiffAlgorithmTest.kt @@ -7,7 +7,7 @@ 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 +18,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 +29,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 +40,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 +51,7 @@ class MyersDiffAlgorithmTest { } @Test - fun `generateDiff with simple example`() { + fun generateDiff_withSimpleExample() { val original = "ABCABAC".toList() val updated = "CBABAC".toList() @@ -73,7 +73,7 @@ class MyersDiffAlgorithmTest { } @Test - fun `generateDiff with DNA example`() { + fun generateDiff_withDnaExample() { val original = "tgtcgctctcaagatggcgtcttattacgaaaggagccagtccgggttgc".toList() val updated = "ggctggggttttcgcacggcgctccctccgcggttgtatctcaggcgaca".toList() @@ -93,7 +93,7 @@ class MyersDiffAlgorithmTest { } @Test - fun `generateDiff with lorem ipsum example`() { + 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 " + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 5c2d1cf016b3885f6930543d57b744ea8c220a1a..62d4c053550b91381bbd28b1afc82d634bf73a8a 100644 GIT binary patch delta 23020 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 delta 19839 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 Date: Tue, 21 Jan 2025 23:57:43 -0500 Subject: [PATCH 2/2] Update ktlint & reformat code --- .editorconfig | 8 + .gitignore | 1 + .../andrewbailey/diff/DiffBenchmarkTest.kt | 6 +- build.gradle | 10 +- .../dev/andrewbailey/diff/DiffGenerator.kt | 120 ++++++------ .../dev/andrewbailey/diff/DiffOperation.kt | 1 - .../dev/andrewbailey/diff/DiffResult.kt | 14 +- .../diff/impl/CircularIntArray.kt | 12 +- .../diff/impl/MyersDiffAlgorithm.kt | 69 +++---- .../diff/impl/MyersDiffOperation.kt | 9 +- .../andrewbailey/diff/DiffGeneratorTest.kt | 175 ++++++++++-------- .../diff/impl/MyersDiffAlgorithmTest.kt | 44 +++-- .../dev/andrewbailey/diff/DiffReceiver.kt | 35 ++-- .../dev/andrewbailey/diff/DiffReceiverTest.kt | 5 +- 14 files changed, 271 insertions(+), 238 deletions(-) create mode 100644 .editorconfig 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/.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/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 5388a6d..c54e710 100644 --- a/build.gradle +++ b/build.gradle @@ -12,7 +12,7 @@ buildscript { 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.3.3' - classpath "org.jlleitschuh.gradle:ktlint-gradle:9.1.1" + classpath "org.jlleitschuh.gradle:ktlint-gradle:12.1.2" classpath "org.jetbrains.dokka:org.jetbrains.dokka.gradle.plugin:2.0.0" } } @@ -29,12 +29,14 @@ allprojects { } ktlint { + version = "1.5.0" android = true verbose = true + additionalEditorconfig = [ + "max_line_length": "100" + ] disabledRules = [ - "no-empty-class-body", - "no-blank-line-before-rbrace", - "no-wildcard-imports", + ] } } 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 5ff5bd3..7d544df 100644 --- a/difference/src/commonTest/kotlin/dev/andrewbailey/diff/DiffGeneratorTest.kt +++ b/difference/src/commonTest/kotlin/dev/andrewbailey/diff/DiffGeneratorTest.kt @@ -1,6 +1,11 @@ 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 @@ -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 ) @@ -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 ) @@ -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 ) @@ -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 ) @@ -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 ) @@ -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 ) @@ -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 ) @@ -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 f718eac..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,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.test.Test import kotlin.test.assertEquals @@ -94,21 +96,25 @@ class MyersDiffAlgorithmTest { @Test 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.").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(" ") + 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 } - }