AlphanumComparator.java 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. /*
  2. * SPDX-FileCopyrightText: 2017 Tobias Kaminsky <tobias@kaminsky.me>
  3. * SPDX-FileCopyrightText: 2012 Daniel Migowski
  4. * SPDX-FileCopyrightText: 2012 Andre Bogus
  5. * SPDX-FileCopyrightText: 2012 David Koelle
  6. * SPDX-License-Identifier: LGPL-2.1-or-later
  7. *
  8. * The Alphanum Algorithm is an improved sorting algorithm for strings
  9. * containing numbers. Instead of sorting numbers in ASCII order like
  10. * a standard sort, this algorithm sorts numbers in numeric order.
  11. *
  12. * The Alphanum Algorithm is discussed at http://www.DaveKoelle.com
  13. */
  14. package third_parties.daveKoeller;
  15. import com.owncloud.android.lib.resources.files.model.ServerFileInterface;
  16. import java.io.File;
  17. import java.io.Serializable;
  18. import java.math.BigInteger;
  19. import java.text.Collator;
  20. import java.util.Comparator;
  21. /*
  22. * This is an updated version with enhancements made by Daniel Migowski, Andre Bogus, and David Koelle
  23. * *
  24. * To convert to use Templates (Java 1.5+):
  25. * - Change "implements Comparator" to "implements Comparator<String>"
  26. * - Change "compare(Object o1, Object o2)" to "compare(String s1, String s2)"
  27. * - Remove the type checking and casting in compare().
  28. *
  29. * To use this class:
  30. * Use the static "sort" method from the java.util.Collections class:
  31. * Collections.sort(your list, new AlphanumComparator());
  32. *
  33. * Adapted to fit
  34. * https://github.com/nextcloud/server/blob/9a4253ef7c34f9dc71a6a9f7828a10df769f0c32/tests/lib/NaturalSortTest.php
  35. * by Tobias Kaminsky
  36. */
  37. public class AlphanumComparator<T> implements Comparator<T>, Serializable {
  38. private static boolean isDigit(char ch) {
  39. return ch >= 48 && ch <= 57;
  40. }
  41. private static boolean isSpecialChar(char ch) {
  42. return ch <= 47 || ch >= 58 && ch <= 64 || ch >= 91 && ch <= 96 || ch >= 123 && ch <= 126;
  43. }
  44. /**
  45. * Length of string is passed in for improved efficiency (only need to calculate it once)
  46. **/
  47. private static String getChunk(String string, int stringLength, int marker) {
  48. StringBuilder chunk = new StringBuilder();
  49. char c = string.charAt(marker);
  50. chunk.append(c);
  51. marker++;
  52. if (isDigit(c)) {
  53. while (marker < stringLength) {
  54. c = string.charAt(marker);
  55. if (!isDigit(c)) {
  56. break;
  57. }
  58. chunk.append(c);
  59. marker++;
  60. }
  61. } else if (!isSpecialChar(c)) {
  62. while (marker < stringLength) {
  63. c = string.charAt(marker);
  64. if (isDigit(c) || isSpecialChar(c)) {
  65. break;
  66. }
  67. chunk.append(c);
  68. marker++;
  69. }
  70. }
  71. return chunk.toString();
  72. }
  73. public static int compare(ServerFileInterface o1, ServerFileInterface o2) {
  74. String s1 = o1.getFileName();
  75. String s2 = o2.getFileName();
  76. return compare(s1, s2);
  77. }
  78. public static int compare(File f1, File f2) {
  79. String s1 = f1.getPath();
  80. String s2 = f2.getPath();
  81. return compare(s1, s2);
  82. }
  83. public int compare(T t1, T t2) {
  84. return compare(t1.toString(), t2.toString());
  85. }
  86. public static int compare(String s1, String s2) {
  87. int thisMarker = 0;
  88. int thatMarker = 0;
  89. int s1Length = s1.length();
  90. int s2Length = s2.length();
  91. while (thisMarker < s1Length && thatMarker < s2Length) {
  92. String thisChunk = getChunk(s1, s1Length, thisMarker);
  93. thisMarker += thisChunk.length();
  94. String thatChunk = getChunk(s2, s2Length, thatMarker);
  95. thatMarker += thatChunk.length();
  96. // If both chunks contain numeric characters, sort them numerically
  97. int result = 0;
  98. if (isDigit(thisChunk.charAt(0)) && isDigit(thatChunk.charAt(0))) {
  99. // extract digits
  100. int thisChunkZeroCount = 0;
  101. boolean zero = true;
  102. int countThis = 0;
  103. while (countThis < (thisChunk.length()) && isDigit(thisChunk.charAt(countThis))) {
  104. if (zero) {
  105. if (Character.getNumericValue(thisChunk.charAt(countThis)) == 0) {
  106. thisChunkZeroCount++;
  107. } else {
  108. zero = false;
  109. }
  110. }
  111. countThis++;
  112. }
  113. int thatChunkZeroCount = 0;
  114. int countThat = 0;
  115. zero = true;
  116. while (countThat < (thatChunk.length()) && isDigit(thatChunk.charAt(countThat))) {
  117. if (zero) {
  118. if (Character.getNumericValue(thatChunk.charAt(countThat)) == 0) {
  119. thatChunkZeroCount++;
  120. } else {
  121. zero = false;
  122. }
  123. }
  124. countThat++;
  125. }
  126. BigInteger thisChunkValue = new BigInteger(thisChunk.substring(0, countThis));
  127. BigInteger thatChunkValue = new BigInteger(thatChunk.substring(0, countThat));
  128. result = thisChunkValue.compareTo(thatChunkValue);
  129. if (result == 0) {
  130. // value is equal, compare leading zeros
  131. result = Integer.compare(thisChunkZeroCount, thatChunkZeroCount);
  132. if (result != 0) {
  133. return result;
  134. }
  135. } else {
  136. return result;
  137. }
  138. } else if (isSpecialChar(thisChunk.charAt(0)) && isSpecialChar(thatChunk.charAt(0))) {
  139. for (int i = 0; i < thisChunk.length(); i++) {
  140. if (thisChunk.charAt(i) == '.' && thatChunk.charAt(i) != '.') {
  141. return -1;
  142. } else if (thatChunk.charAt(i) == '.' && thisChunk.charAt(i) != '.') {
  143. return 1;
  144. } else {
  145. result = thisChunk.charAt(i) - thatChunk.charAt(i);
  146. if (result != 0) {
  147. return result;
  148. }
  149. }
  150. }
  151. } else if (isSpecialChar(thisChunk.charAt(0)) && !isSpecialChar(thatChunk.charAt(0))) {
  152. return -1;
  153. } else if (!isSpecialChar(thisChunk.charAt(0)) && isSpecialChar(thatChunk.charAt(0))) {
  154. return 1;
  155. } else {
  156. result = Collator.getInstance().compare(thisChunk, thatChunk);
  157. }
  158. if (result != 0) {
  159. return result;
  160. }
  161. }
  162. return s1Length - s2Length;
  163. }
  164. }