mirror of
https://github.com/nextcloud/android.git
synced 2024-11-30 08:25:21 +03:00
Merge pull request #2262 from nextcloud/naturalSorting
Add natural sorting
This commit is contained in:
commit
aa1f5f8166
3 changed files with 160 additions and 57 deletions
|
@ -28,6 +28,7 @@ import android.content.Context;
|
|||
import android.net.Uri;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.content.FileProvider;
|
||||
|
||||
import com.owncloud.android.R;
|
||||
|
@ -36,7 +37,6 @@ import com.owncloud.android.lib.common.utils.Log_OC;
|
|||
import com.owncloud.android.utils.MimeType;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Locale;
|
||||
|
||||
import third_parties.daveKoeller.AlphanumComparator;
|
||||
|
||||
|
@ -269,6 +269,18 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
|
|||
return mMimeType != null && mMimeType.equals(MimeType.DIRECTORY);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets mimetype to folder and returns this file
|
||||
* Only for testing
|
||||
*
|
||||
* @return OCFile this file
|
||||
*/
|
||||
public OCFile setFolder() {
|
||||
setMimetype(MimeType.DIRECTORY);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to check if this file is available locally
|
||||
*
|
||||
|
@ -618,9 +630,9 @@ public class OCFile implements Parcelable, Comparable<OCFile> {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(OCFile another) {
|
||||
public int compareTo(@NonNull OCFile another) {
|
||||
if (isFolder() && another.isFolder()) {
|
||||
return getRemotePath().toLowerCase(Locale.ROOT).compareTo(another.getRemotePath().toLowerCase(Locale.ROOT));
|
||||
return new AlphanumComparator().compare(this, another);
|
||||
} else if (isFolder()) {
|
||||
return -1;
|
||||
} else if (another.isFolder()) {
|
||||
|
|
|
@ -27,9 +27,9 @@ package third_parties.daveKoeller;
|
|||
import com.owncloud.android.datamodel.OCFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.Serializable;
|
||||
import java.text.Collator;
|
||||
import java.util.Comparator;
|
||||
import java.util.Locale;
|
||||
|
||||
/*
|
||||
* This is an updated version with enhancements made by Daniel Migowski, Andre Bogus, and David Koelle
|
||||
|
@ -47,7 +47,7 @@ import java.util.Locale;
|
|||
* https://github.com/nextcloud/server/blob/9a4253ef7c34f9dc71a6a9f7828a10df769f0c32/tests/lib/NaturalSortTest.php
|
||||
* by Tobias Kaminsky
|
||||
*/
|
||||
public class AlphanumComparator<T> implements Comparator<T> {
|
||||
public class AlphanumComparator<T> implements Comparator<T>, Serializable {
|
||||
private boolean isDigit(char ch) {
|
||||
return ch >= 48 && ch <= 57;
|
||||
}
|
||||
|
@ -87,15 +87,15 @@ public class AlphanumComparator<T> implements Comparator<T> {
|
|||
}
|
||||
|
||||
public int compare(OCFile o1, OCFile o2) {
|
||||
String s1 = o1.getRemotePath().toLowerCase(Locale.ROOT);
|
||||
String s2 = o2.getRemotePath().toLowerCase(Locale.ROOT);
|
||||
String s1 = o1.getFileName();
|
||||
String s2 = o2.getFileName();
|
||||
|
||||
return compare(s1, s2);
|
||||
}
|
||||
|
||||
public int compare(File f1, File f2) {
|
||||
String s1 = f1.getPath().toLowerCase(Locale.ROOT);
|
||||
String s2 = f2.getPath().toLowerCase(Locale.ROOT);
|
||||
String s1 = f1.getPath();
|
||||
String s2 = f2.getPath();
|
||||
|
||||
return compare(s1, s2);
|
||||
}
|
||||
|
@ -120,17 +120,48 @@ public class AlphanumComparator<T> implements Comparator<T> {
|
|||
// If both chunks contain numeric characters, sort them numerically
|
||||
int result = 0;
|
||||
if (isDigit(thisChunk.charAt(0)) && isDigit(thatChunk.charAt(0))) {
|
||||
// Simple chunk comparison by length.
|
||||
int thisChunkLength = thisChunk.length();
|
||||
result = thisChunkLength - thatChunk.length();
|
||||
// If equal, the first different number counts
|
||||
if (result == 0) {
|
||||
for (int i = 0; i < thisChunkLength; i++) {
|
||||
result = thisChunk.charAt(i) - thatChunk.charAt(i);
|
||||
if (result != 0) {
|
||||
return result;
|
||||
// extract digits
|
||||
int thisChunkZeroCount = 0;
|
||||
boolean zero = true;
|
||||
int c = 0;
|
||||
while (c < (thisChunk.length()) && isDigit(thisChunk.charAt(c))) {
|
||||
if (zero) {
|
||||
if (Character.getNumericValue(thisChunk.charAt(c)) == 0) {
|
||||
thisChunkZeroCount++;
|
||||
} else {
|
||||
zero = false;
|
||||
}
|
||||
}
|
||||
c++;
|
||||
}
|
||||
int thisChunkValue = Integer.parseInt(thisChunk.substring(0, c));
|
||||
|
||||
int thatChunkZeroCount = 0;
|
||||
c = 0;
|
||||
zero = true;
|
||||
while (c < (thatChunk.length()) && isDigit(thatChunk.charAt(c))) {
|
||||
if (zero) {
|
||||
if (Character.getNumericValue(thatChunk.charAt(c)) == 0) {
|
||||
thatChunkZeroCount++;
|
||||
} else {
|
||||
zero = false;
|
||||
}
|
||||
}
|
||||
c++;
|
||||
}
|
||||
int thatChunkValue = Integer.parseInt(thatChunk.substring(0, c));
|
||||
|
||||
result = Integer.compare(thisChunkValue, thatChunkValue);
|
||||
|
||||
if (result == 0) {
|
||||
// value is equal, compare leading zeros
|
||||
result = Integer.compare(thisChunkZeroCount, thatChunkZeroCount);
|
||||
|
||||
if (result != 0) {
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
} else if (isSpecialChar(thisChunk.charAt(0)) && isSpecialChar(thatChunk.charAt(0))) {
|
||||
for (int i = 0; i < thisChunk.length(); i++) {
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
package com.owncloud.android.utils;
|
||||
|
||||
import com.owncloud.android.datamodel.OCFile;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import third_parties.daveKoeller.AlphanumComparator;
|
||||
|
@ -42,71 +46,127 @@ public class TestSorting {
|
|||
|
||||
@Test
|
||||
public void testSpecialChars() {
|
||||
String[] sortedArray = {"[Test] Folder", "01 - January", "11 - November", "Ôle",
|
||||
"Test 1", "Test 01", "Test 04", "Üüü",
|
||||
"z.[Test], z. Test"};
|
||||
|
||||
String[] unsortedArray = {"11 - November", "Test 04", "Test 01", "Ôle", "Üüü", "01 - Januar", "[Test] Folder",
|
||||
"z.[Test]", "z. Test"};
|
||||
|
||||
String[] sortedArray = {"[Test] Folder", "01 - Januar", "11 - November", "Ôle", "Test 01", "Test 04", "Üüü",
|
||||
"z. Test", "z.[Test]"};
|
||||
|
||||
assertTrue(sortAndTest(unsortedArray, sortedArray));
|
||||
assertTrue(sortAndTest(Arrays.asList(sortedArray)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifferentCasing() {
|
||||
String[] unsortedArray = {"aaa", "bbb", "BBB", "AAA"};
|
||||
String[] sortedArray = {"aaa", "AAA", "bbb", "BBB"};
|
||||
|
||||
assertTrue(sortAndTest(unsortedArray, sortedArray));
|
||||
assertTrue(sortAndTest(Arrays.asList(sortedArray)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNumbers() {
|
||||
String[] unsortedArray = {"124.txt", "abc1", "123.txt", "abc", "abc2", "def (2).txt", "ghi 10.txt", "abc12",
|
||||
"def.txt", "def (1).txt", "ghi 2.txt", "def (10).txt", "abc10", "def (12).txt", "z", "ghi.txt", "za",
|
||||
"ghi 1.txt", "ghi 12.txt", "zz", "15.txt", "15b.txt"};
|
||||
public void testLeadingZeros() {
|
||||
String[] sortedArray = {"T 0 abc", "T 00 abc", "T 000 abc", "T 1 abc", "T 01 abc",
|
||||
"T 001 abc", "T 2 abc", "T 02 abc", "T 3 abc", "T 03 abc"};
|
||||
|
||||
String[] sortedArray = {"15.txt", "15b.txt", "123.txt", "124.txt", "abc", "abc1", "abc2", "abc10", "abc12",
|
||||
"def.txt", "def (1).txt", "def (2).txt", "def (10).txt", "def (12).txt", "ghi.txt", "ghi 1.txt",
|
||||
"ghi 2.txt", "ghi 10.txt", "ghi 12.txt", "z", "za", "zz"};
|
||||
|
||||
assertTrue(sortAndTest(unsortedArray, sortedArray));
|
||||
assertTrue(sortAndTest(Arrays.asList(sortedArray)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChineseCharacters() {
|
||||
String[] unsortedArray = {"十.txt", "一.txt", "二.txt", "十 2.txt", "三.txt", "四.txt", "abc.txt", "五.txt",
|
||||
"七.txt", "八.txt", "九.txt", "六.txt", "十一.txt", "波.txt", "破.txt", "莫.txt", "啊.txt", "123.txt"};
|
||||
public void testTrailingDigits() {
|
||||
String[] unsortedArray = {"Zeros 2", "Zeros", "T 2", "T", "T 01", "T 003", "A"};
|
||||
String[] sortedArray = {"A", "T", "T 01", "T 2", "T 003", "Zeros", "Zeros 2"};
|
||||
|
||||
String[] sortedArray = {"123.txt", "abc.txt", "一.txt", "七.txt", "三.txt", "九.txt", "二.txt", "五.txt",
|
||||
"八.txt", "六.txt", "十.txt", "十 2.txt", "十一.txt", "啊.txt", "四.txt", "波.txt", "破.txt", "莫.txt"};
|
||||
|
||||
assertTrue(sortAndTest(unsortedArray, sortedArray));
|
||||
assertTrue(sortAndTest(Arrays.asList(sortedArray)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWithUmlauts() {
|
||||
String[] unsortedArray = {"öh.txt", "Äh.txt", "oh.txt", "Üh 2.txt", "Üh.txt", "ah.txt", "Öh.txt", "uh.txt",
|
||||
"üh.txt", "äh.txt"};
|
||||
String[] sortedArray = {"ah.txt", "äh.txt", "Äh.txt", "oh.txt", "öh.txt", "Öh.txt", "uh.txt", "üh.txt",
|
||||
"Üh.txt", "Üh 2.txt"};
|
||||
public void testOCFilesWithFolderFirst() {
|
||||
List<OCFile> sortedArray = new ArrayList<>();
|
||||
sortedArray.add(new OCFile("/ah.txt").setFolder());
|
||||
sortedArray.add(new OCFile("/Äh.txt").setFolder());
|
||||
sortedArray.add(new OCFile("/oh.txt").setFolder());
|
||||
sortedArray.add(new OCFile("/öh.txt").setFolder());
|
||||
sortedArray.add(new OCFile("/üh.txt").setFolder());
|
||||
sortedArray.add(new OCFile("/Üh.txt").setFolder());
|
||||
sortedArray.add(new OCFile("/äh.txt"));
|
||||
sortedArray.add(new OCFile("/Öh.txt"));
|
||||
sortedArray.add(new OCFile("/uh.txt"));
|
||||
sortedArray.add(new OCFile("/Üh 2.txt"));
|
||||
|
||||
assertTrue(sortAndTest(unsortedArray, sortedArray));
|
||||
assertTrue(sortAndTest(sortedArray));
|
||||
}
|
||||
|
||||
private boolean sortAndTest(String[] unsortedArray, String[] sortedArray) {
|
||||
List<String> unsortedList = Arrays.asList(unsortedArray);
|
||||
List<String> sortedList = Arrays.asList(sortedArray);
|
||||
/**
|
||||
* uses OCFile.compareTo() instead of custom comparator
|
||||
*/
|
||||
@Test
|
||||
public void testOCFiles() {
|
||||
List<OCFile> sortedArray = new ArrayList<>();
|
||||
sortedArray.add(new OCFile("/ah.txt").setFolder());
|
||||
sortedArray.add(new OCFile("/Äh.txt").setFolder());
|
||||
sortedArray.add(new OCFile("/oh.txt").setFolder());
|
||||
sortedArray.add(new OCFile("/öh.txt").setFolder());
|
||||
sortedArray.add(new OCFile("/üh.txt").setFolder());
|
||||
sortedArray.add(new OCFile("/Üh.txt").setFolder());
|
||||
sortedArray.add(new OCFile("/äh.txt"));
|
||||
sortedArray.add(new OCFile("/Öh.txt"));
|
||||
sortedArray.add(new OCFile("/uh.txt"));
|
||||
sortedArray.add(new OCFile("/Üh 2.txt"));
|
||||
|
||||
List unsortedList = shuffle(sortedArray);
|
||||
Collections.sort(unsortedList);
|
||||
|
||||
Collections.sort(unsortedList, new AlphanumComparator());
|
||||
assertTrue(test(sortedArray, unsortedList));
|
||||
}
|
||||
|
||||
for (int i = 0; i < sortedList.size(); i++) {
|
||||
if (sortedList.get(i).compareTo(unsortedList.get(i)) != 0) {
|
||||
private List<Comparable> shuffle(List<? extends Comparable> files) {
|
||||
List<Comparable> shuffled = new ArrayList<>();
|
||||
shuffled.addAll(files);
|
||||
|
||||
System.out.println(" target: " + sortedList.toString());
|
||||
System.out.println(" actual: " + unsortedList.toString());
|
||||
Collections.shuffle(shuffled);
|
||||
|
||||
return shuffled;
|
||||
}
|
||||
|
||||
private boolean sortAndTest(List<? extends Comparable> sortedList) {
|
||||
return test(sortedList, sort(sortedList));
|
||||
}
|
||||
|
||||
private List<Comparable> sort(List<? extends Comparable> sortedList) {
|
||||
List unsortedList = shuffle(sortedList);
|
||||
|
||||
if (sortedList.get(0) instanceof OCFile) {
|
||||
Collections.sort(unsortedList, (Comparator<OCFile>) (o1, o2) -> {
|
||||
if (o1.isFolder() && o2.isFolder()) {
|
||||
return new AlphanumComparator().compare(o1, o2);
|
||||
} else if (o1.isFolder()) {
|
||||
return -1;
|
||||
} else if (o2.isFolder()) {
|
||||
return 1;
|
||||
}
|
||||
return new AlphanumComparator().compare(o1, o2);
|
||||
});
|
||||
} else {
|
||||
Collections.sort(unsortedList, new AlphanumComparator<>());
|
||||
}
|
||||
|
||||
return unsortedList;
|
||||
}
|
||||
|
||||
private boolean test(List<? extends Comparable> target, List<? extends Comparable> actual) {
|
||||
|
||||
for (int i = 0; i < target.size(); i++) {
|
||||
int compare;
|
||||
|
||||
if (target.get(i) instanceof OCFile) {
|
||||
String sortedName = ((OCFile) target.get(i)).getFileName();
|
||||
String unsortedName = ((OCFile) actual.get(i)).getFileName();
|
||||
compare = sortedName.compareTo(unsortedName);
|
||||
} else {
|
||||
compare = target.get(i).compareTo(actual.get(i));
|
||||
}
|
||||
|
||||
if (compare != 0) {
|
||||
|
||||
System.out.println(" target: \n" + target.toString());
|
||||
System.out.println(" actual: \n" + actual.toString());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue