|
16 | 16 | *
|
17 | 17 | * This file has been modified by Peter Trifanov when porting from Java to Kotlin.
|
18 | 18 | */
|
19 |
| -/** |
20 |
| - * Implements the difference and patching engine |
21 |
| - */ |
22 |
| - |
23 |
| -@file:Suppress("TooManyFunctions") |
| 19 | +@file:JvmName("DiffUtils") |
24 | 20 |
|
25 | 21 | package io.github.petertrr.diffutils
|
26 | 22 |
|
27 | 23 | import io.github.petertrr.diffutils.algorithm.DiffAlgorithm
|
28 | 24 | import io.github.petertrr.diffutils.algorithm.DiffAlgorithmListener
|
| 25 | +import io.github.petertrr.diffutils.algorithm.NoopAlgorithmListener |
29 | 26 | import io.github.petertrr.diffutils.algorithm.myers.MyersDiff
|
30 | 27 | import io.github.petertrr.diffutils.patch.Patch
|
31 | 28 | import io.github.petertrr.diffutils.patch.PatchFailedException
|
| 29 | +import io.github.petertrr.diffutils.text.DiffRowGenerator |
| 30 | +import kotlin.jvm.JvmName |
| 31 | +import kotlin.jvm.JvmOverloads |
32 | 32 |
|
33 |
| -/** |
34 |
| - * Computes the difference between the original and revised list of elements with default diff |
35 |
| - * algorithm |
36 |
| - * |
37 |
| - * @param T types to be diffed |
38 |
| - * @param original The original text. |
39 |
| - * @param revised The revised text. |
40 |
| - * @param progress progress listener |
41 |
| - * @return The patch describing the difference between the original and revised sequences. |
42 |
| - */ |
43 |
| -public fun <T> diff(original: List<T>, revised: List<T>, progress: DiffAlgorithmListener?): Patch<T> { |
44 |
| - return diff(original, revised, MyersDiff(), progress) |
45 |
| -} |
46 |
| - |
47 |
| -public fun <T> diff(original: List<T>, revised: List<T>): Patch<T> { |
48 |
| - return diff(original, revised, MyersDiff(), null) |
49 |
| -} |
50 |
| - |
51 |
| -public fun <T> diff(original: List<T>, revised: List<T>, includeEqualParts: Boolean): Patch<T> { |
52 |
| - return diff(original, revised, MyersDiff(), null, includeEqualParts) |
53 |
| -} |
| 33 | +// Instead of asking consumers to normalize their line endings, we simply catch them all. |
| 34 | +private val lineBreak = Regex("\r\n|\r|\n") |
54 | 35 |
|
55 | 36 | /**
|
56 |
| - * Computes the difference between the original and revised text. |
| 37 | + * Computes the difference between the source and target text. |
| 38 | + * |
| 39 | + * By default, uses the Myers algorithm. |
| 40 | + * |
| 41 | + * @param sourceText The original text |
| 42 | + * @param targetText The target text |
| 43 | + * @param algorithm The diff algorithm to use |
| 44 | + * @param progress The diff algorithm progress listener |
| 45 | + * @param includeEqualParts Whether to include equal data parts into the patch. `false` by default. |
| 46 | + * @return The patch describing the difference between the original and target text |
57 | 47 | */
|
| 48 | +@JvmOverloads |
58 | 49 | public fun diff(
|
59 | 50 | sourceText: String,
|
60 | 51 | targetText: String,
|
61 |
| - progress: DiffAlgorithmListener? |
62 |
| -): Patch<String> { |
63 |
| - return diff( |
64 |
| - sourceText.split("\n"), |
65 |
| - targetText.split("\n"), |
66 |
| - progress |
| 52 | + algorithm: DiffAlgorithm<String> = MyersDiff(), |
| 53 | + progress: DiffAlgorithmListener = NoopAlgorithmListener(), |
| 54 | + includeEqualParts: Boolean = false, |
| 55 | +): Patch<String> = |
| 56 | + diff( |
| 57 | + source = sourceText.split(lineBreak), |
| 58 | + target = targetText.split(lineBreak), |
| 59 | + algorithm = algorithm, |
| 60 | + progress = progress, |
| 61 | + includeEqualParts = includeEqualParts, |
67 | 62 | )
|
68 |
| -} |
69 | 63 |
|
70 | 64 | /**
|
71 |
| - * Computes the difference between the original and revised list of elements with default diff |
72 |
| - * algorithm |
73 |
| - * |
74 |
| - * @param source The original text. |
75 |
| - * @param target The revised text. |
| 65 | + * Computes the difference between the source and target list of elements using the Myers algorithm. |
76 | 66 | *
|
77 |
| - * @param equalizer the equalizer object to replace the default compare algorithm |
78 |
| - * (Object.equals). If `null` the default equalizer of the default algorithm is used.. |
79 |
| - * @return The patch describing the difference between the original and revised sequences. |
| 67 | + * @param source The original elements |
| 68 | + * @param target The target elements |
| 69 | + * @param equalizer The equalizer to replace the default compare algorithm [Any.equals]. |
| 70 | + * If `null`, the default equalizer of the default algorithm is used. |
| 71 | + * @return The patch describing the difference between the source and target sequences |
80 | 72 | */
|
81 | 73 | public fun <T> diff(
|
82 | 74 | source: List<T>,
|
83 | 75 | target: List<T>,
|
84 |
| - equalizer: ((T, T) -> Boolean)? |
85 |
| -): Patch<T> { |
86 |
| - return if (equalizer != null) { |
87 |
| - diff( |
88 |
| - source, |
89 |
| - target, |
90 |
| - MyersDiff(equalizer) |
91 |
| - ) |
92 |
| - } else { |
93 |
| - diff(source, target, MyersDiff()) |
94 |
| - } |
95 |
| -} |
96 |
| - |
97 |
| -public fun <T> diff( |
98 |
| - original: List<T>, |
99 |
| - revised: List<T>, |
100 |
| - algorithm: DiffAlgorithm<T>, |
101 |
| - progress: DiffAlgorithmListener? |
102 |
| -): Patch<T> { |
103 |
| - return diff(original, revised, algorithm, progress, false) |
104 |
| -} |
| 76 | + equalizer: ((T, T) -> Boolean), |
| 77 | +): Patch<T> = |
| 78 | + diff( |
| 79 | + source = source, |
| 80 | + target = target, |
| 81 | + algorithm = MyersDiff(equalizer), |
| 82 | + ) |
105 | 83 |
|
106 | 84 | /**
|
107 |
| - * Computes the difference between the original and revised list of elements with default diff |
108 |
| - * algorithm |
| 85 | + * Computes the difference between the original and target list of elements. |
109 | 86 | *
|
110 |
| - * @param original The original text. Must not be `null`. |
111 |
| - * @param revised The revised text. Must not be `null`. |
112 |
| - * @param algorithm The diff algorithm. Must not be `null`. |
113 |
| - * @param progress The diff algorithm listener. |
114 |
| - * @param includeEqualParts Include equal data parts into the patch. |
115 |
| - * @return The patch describing the difference between the original and revised sequences. Never |
116 |
| - * `null`. |
117 |
| - */ |
118 |
| -public fun <T> diff( |
119 |
| - original: List<T>, |
120 |
| - revised: List<T>, |
121 |
| - algorithm: DiffAlgorithm<T>, |
122 |
| - progress: DiffAlgorithmListener?, |
123 |
| - includeEqualParts: Boolean |
124 |
| -): Patch<T> { |
125 |
| - return Patch.generate(original, revised, algorithm.computeDiff(original, revised, progress), includeEqualParts) |
126 |
| -} |
127 |
| - |
128 |
| -/** |
129 |
| - * Computes the difference between the original and revised list of elements with default diff |
130 |
| - * algorithm |
| 87 | + * By default, uses the Meyers algorithm. |
131 | 88 | *
|
132 |
| - * @param original The original text. Must not be `null`. |
133 |
| - * @param revised The revised text. Must not be `null`. |
134 |
| - * @param algorithm The diff algorithm. Must not be `null`. |
135 |
| - * @return The patch describing the difference between the original and revised sequences. Never |
136 |
| - * `null`. |
| 89 | + * @param source The original elements |
| 90 | + * @param target The target elements |
| 91 | + * @param algorithm The diff algorithm to use |
| 92 | + * @param progress The diff algorithm progress listener |
| 93 | + * @param includeEqualParts Whether to include equal data parts into the patch. `false` by default. |
| 94 | + * @return The patch describing the difference between the original and target sequences |
137 | 95 | */
|
138 |
| -public fun <T> diff(original: List<T>, revised: List<T>, algorithm: DiffAlgorithm<T>): Patch<T> { |
139 |
| - return diff(original, revised, algorithm, null) |
140 |
| -} |
| 96 | +@JvmOverloads |
| 97 | +public fun <T> diff( |
| 98 | + source: List<T>, |
| 99 | + target: List<T>, |
| 100 | + algorithm: DiffAlgorithm<T> = MyersDiff(), |
| 101 | + progress: DiffAlgorithmListener = NoopAlgorithmListener(), |
| 102 | + includeEqualParts: Boolean = false, |
| 103 | +): Patch<T> = |
| 104 | + Patch.generate( |
| 105 | + original = source, |
| 106 | + revised = target, |
| 107 | + changes = algorithm.computeDiff(source, target, progress), |
| 108 | + includeEquals = includeEqualParts, |
| 109 | + ) |
141 | 110 |
|
142 | 111 | /**
|
143 |
| - * Computes the difference between the given texts inline. This one uses the "trick" to make out |
144 |
| - * of texts lists of characters, like DiffRowGenerator does and merges those changes at the end |
145 |
| - * together again. |
| 112 | + * Computes the difference between the given texts inline. |
146 | 113 | *
|
147 |
| - * @param original |
148 |
| - * @param revised |
149 |
| - * @return |
| 114 | + * This one uses the "trick" to make out of texts lists of characters, |
| 115 | + * like [DiffRowGenerator] does and merges those changes at the end together again. |
150 | 116 | */
|
151 | 117 | public fun diffInline(original: String, revised: String): Patch<String> {
|
152 |
| - val origList: MutableList<String> = arrayListOf() |
153 |
| - val revList: MutableList<String> = arrayListOf() |
154 |
| - for (character in original.toCharArray()) { |
| 118 | + val origChars = original.toCharArray() |
| 119 | + val origList = ArrayList<String>(origChars.size) |
| 120 | + |
| 121 | + val revChars = revised.toCharArray() |
| 122 | + val revList = ArrayList<String>(revChars.size) |
| 123 | + |
| 124 | + for (character in origChars) { |
155 | 125 | origList.add(character.toString())
|
156 | 126 | }
|
157 |
| - for (character in revised.toCharArray()) { |
| 127 | + |
| 128 | + for (character in revChars) { |
158 | 129 | revList.add(character.toString())
|
159 | 130 | }
|
160 |
| - val patch: Patch<String> = diff(origList, revList) |
161 |
| - patch.deltas.map { delta -> |
162 |
| - delta.withChunks( |
163 |
| - delta.source.copy(lines = compressLines(delta.source.lines, "")), |
164 |
| - delta.target.copy(lines = compressLines(delta.target.lines, "")) |
| 131 | + |
| 132 | + val patch = diff(origList, revList) |
| 133 | + patch.deltas = patch.deltas.mapTo(ArrayList(patch.deltas.size)) { |
| 134 | + it.withChunks( |
| 135 | + it.source.copy(lines = compressLines(it.source.lines, "")), |
| 136 | + it.target.copy(lines = compressLines(it.target.lines, "")), |
165 | 137 | )
|
166 | 138 | }
|
167 |
| - .let { patch.deltas = it.toMutableList() } |
168 |
| - return patch |
169 |
| -} |
170 | 139 |
|
171 |
| -private fun compressLines(lines: List<String>, delimiter: String): List<String> { |
172 |
| - return if (lines.isEmpty()) { |
173 |
| - emptyList() |
174 |
| - } else { |
175 |
| - listOf(lines.joinToString(delimiter)) |
176 |
| - } |
| 140 | + return patch |
177 | 141 | }
|
178 | 142 |
|
179 | 143 | /**
|
180 |
| - * Patch the original text with given patch |
| 144 | + * Patch the original text with the given patch. |
181 | 145 | *
|
182 |
| - * @param original the original text |
183 |
| - * @param patch the given patch |
184 |
| - * @return the revised text |
185 |
| - * @throws PatchFailedException if can't apply patch |
| 146 | + * @param original The original text |
| 147 | + * @param patch The patch to apply |
| 148 | + * @return The revised text |
| 149 | + * @throws PatchFailedException If the patch cannot be applied |
186 | 150 | */
|
187 |
| -@Throws(PatchFailedException::class) |
188 |
| -public fun <T> patch(original: List<T>, patch: Patch<T>): List<T> { |
189 |
| - return patch.applyTo(original) |
190 |
| -} |
| 151 | +public fun <T> patch(original: List<T>, patch: Patch<T>): List<T> = |
| 152 | + patch.applyTo(original) |
191 | 153 |
|
192 | 154 | /**
|
193 | 155 | * Unpatch the revised text for a given patch
|
194 | 156 | *
|
195 |
| - * @param revised the revised text |
196 |
| - * @param patch the given patch |
197 |
| - * @return the original text |
| 157 | + * @param revised The revised text |
| 158 | + * @param patch The given patch |
| 159 | + * @return The original text |
198 | 160 | */
|
199 |
| -@Suppress("UNUSED") |
200 |
| -public fun <T> unpatch(revised: List<T>, patch: Patch<T>): List<T> { |
201 |
| - return patch.restore(revised) |
202 |
| -} |
| 161 | +public fun <T> unpatch(revised: List<T>, patch: Patch<T>): List<T> = |
| 162 | + patch.restore(revised) |
| 163 | + |
| 164 | +private fun compressLines(lines: List<String>, delimiter: String): List<String> = |
| 165 | + if (lines.isEmpty()) { |
| 166 | + emptyList() |
| 167 | + } else { |
| 168 | + listOf(lines.joinToString(delimiter)) |
| 169 | + } |
0 commit comments