77package com.owncloud.android.utils.sort
88
99import com.owncloud.android.lib.resources.files.model.ServerFileInterface
10- import java.math.BigInteger
1110import java.text.Collator
1211
1312/* *
1413 * Sorts names the way people read them, so `abc2` comes before `abc10` instead of after it.
14+ *
1515 */
1616class AlphanumericComparator <T : Any > : Comparator <T > {
1717
@@ -24,67 +24,67 @@ class AlphanumericComparator<T : Any> : Comparator<T> {
2424 compare(first.fileName, second.fileName)
2525
2626 @JvmStatic
27- fun compare (first : String , second : String ): Int {
28- if (first == second) {
29- return 0
30- }
31-
32- val ourChunks = chunks(first)
33- val theirChunks = chunks(second)
27+ fun compare (first : String , second : String ): Int = if (first == second) 0 else compareByChunks(first, second)
3428
35- val byChunk = ourChunks.asSequence()
36- .zip(theirChunks.asSequence()) { ours, theirs -> compareChunks(ours, theirs) }
37- .firstOrNull { it ! = 0 }
29+ private fun compareByChunks ( first : String , second : String ): Int {
30+ var ourStart = 0
31+ var theirStart = 0
3832
39- return byChunk
40- ? : ourChunks.size.compareTo(theirChunks.size).takeIf { it != 0 }
41- ? : first.compareTo(second)
42- }
43-
44- private val collator = ThreadLocal .withInitial { Collator .getInstance() }
33+ while (ourStart < first.length && theirStart < second.length) {
34+ val ours = chunkAt(first, ourStart)
35+ val theirs = chunkAt(second, theirStart)
4536
46- private fun chunks (name : String ): List <String > {
47- val chunks = mutableListOf<String >()
48- var start = 0
37+ val byChunk = compareChunks(ours, theirs)
38+ if (byChunk != 0 ) {
39+ return byChunk
40+ }
4941
50- while (start < name.length) {
51- val end = chunkEnd(name, start)
52- chunks + = name.substring(start, end)
53- start = end
42+ ourStart + = ours.length
43+ theirStart + = theirs.length
5444 }
5545
56- return chunks
46+ return when (val byLength = first.length.compareTo(second.length)) {
47+ 0 -> first.compareTo(second)
48+ else -> byLength
49+ }
5750 }
5851
59- private fun chunkEnd ( name : String , start : Int ): Int {
60- if (name[start].isSeparator()) {
61- return start + 1
62- }
52+ private val collators : ThreadLocal < Collator > = ThreadLocal .withInitial { Collator .getInstance() }
53+
54+ private val collator : Collator
55+ get() = collators.get() ? : Collator .getInstance()
6356
64- val digitRun = name[start].isAsciiDigit()
57+ private fun chunkAt (name : String , start : Int ): String {
58+ val kind = kindOf(name[start])
6559 var end = start + 1
66- while (end < name.length && ! name[end].isSeparator() && name[end].isAsciiDigit() == digitRun) {
67- end++
60+
61+ if (kind != ChunkKind .SEPARATOR ) {
62+ while (end < name.length && kindOf(name[end]) == kind) {
63+ end++
64+ }
6865 }
69- return end
66+
67+ return name.substring(start, end)
7068 }
7169
7270 private fun compareChunks (ours : String , theirs : String ): Int {
73- val ourRank = rankOf(ours)
74- val byRank = ourRank.compareTo(rankOf(theirs))
75-
76- return when {
77- byRank != 0 -> byRank
78- ourRank == RANK_SEPARATOR -> compareSeparators(ours[0 ], theirs[0 ])
79- ourRank == RANK_NUMBER -> compareNumbers(ours, theirs)
80- else -> compareText(ours, theirs)
71+ val kind = kindOf(ours[0 ])
72+ val byKind = kind.compareTo(kindOf(theirs[0 ]))
73+ if (byKind != 0 ) {
74+ return byKind
75+ }
76+
77+ return when (kind) {
78+ ChunkKind .SEPARATOR -> compareSeparators(ours[0 ], theirs[0 ])
79+ ChunkKind .NUMBER -> compareNumbers(ours, theirs)
80+ ChunkKind .TEXT -> compareText(ours, theirs)
8181 }
8282 }
8383
84- private fun rankOf ( chunk : String ): Int = when {
85- chunk[ 0 ].isSeparator () -> RANK_SEPARATOR
86- chunk[ 0 ].isAsciiDigit() -> RANK_NUMBER
87- else -> RANK_TEXT
84+ private fun kindOf ( char : Char ): ChunkKind = when {
85+ char <= LAST_ASCII_PUNCTUATION && ! char.isLetterOrDigit () -> ChunkKind . SEPARATOR
86+ char in ZERO .. NINE -> ChunkKind . NUMBER
87+ else -> ChunkKind . TEXT
8888 }
8989
9090 private fun compareSeparators (ours : Char , theirs : Char ): Int = when {
@@ -94,16 +94,23 @@ class AlphanumericComparator<T : Any> : Comparator<T> {
9494 else -> ours.compareTo(theirs)
9595 }
9696
97- private fun compareNumbers (ours : String , theirs : String ): Int =
98- when (val byValue = BigInteger (ours).compareTo(BigInteger (theirs))) {
99- 0 -> leadingZeroes(ours).compareTo(leadingZeroes(theirs))
100- else -> byValue
97+ private fun compareNumbers (ours : String , theirs : String ): Int {
98+ val ourValue = ours.trimStart(ZERO )
99+ val theirValue = theirs.trimStart(ZERO )
100+
101+ val byDigitCount = ourValue.length.compareTo(theirValue.length)
102+ if (byDigitCount != 0 ) {
103+ return byDigitCount
101104 }
102105
103- private fun leadingZeroes (digits : String ): Int = digits.takeWhile { it == ZERO }.length
106+ return when (val byValue = ourValue.compareTo(theirValue)) {
107+ 0 -> ours.length.compareTo(theirs.length)
108+ else -> byValue
109+ }
110+ }
104111
105112 private fun compareText (ours : String , theirs : String ): Int {
106- val byCollation = collator.get()?. compare(ours, theirs) ? : 0
113+ val byCollation = collator.compare(ours, theirs)
107114 if (byCollation != 0 ) {
108115 return byCollation
109116 }
@@ -114,27 +121,9 @@ class AlphanumericComparator<T : Any> : Comparator<T> {
114121 }
115122 }
116123
117- private fun Char.isAsciiDigit (): Boolean = this in ZERO .. NINE
118-
119- private fun Char.isSeparator (): Boolean = this <= LAST_CONTROL_OR_PUNCTUATION ||
120- this in FIRST_PUNCTUATION_AFTER_DIGITS .. LAST_PUNCTUATION_BEFORE_UPPERCASE ||
121- this in FIRST_PUNCTUATION_AFTER_UPPERCASE .. LAST_PUNCTUATION_BEFORE_LOWERCASE ||
122- this in FIRST_PUNCTUATION_AFTER_LOWERCASE .. LAST_ASCII_PUNCTUATION
123-
124- private const val RANK_SEPARATOR = 0
125- private const val RANK_NUMBER = 1
126- private const val RANK_TEXT = 2
127-
128124 private const val DOT = ' .'
129125 private const val ZERO = ' 0'
130126 private const val NINE = ' 9'
131-
132- private const val LAST_CONTROL_OR_PUNCTUATION = ' /'
133- private const val FIRST_PUNCTUATION_AFTER_DIGITS = ' :'
134- private const val LAST_PUNCTUATION_BEFORE_UPPERCASE = ' @'
135- private const val FIRST_PUNCTUATION_AFTER_UPPERCASE = ' ['
136- private const val LAST_PUNCTUATION_BEFORE_LOWERCASE = ' `'
137- private const val FIRST_PUNCTUATION_AFTER_LOWERCASE = ' {'
138127 private const val LAST_ASCII_PUNCTUATION = ' ~'
139128 }
140129}
0 commit comments