From 3557e4b43c3617a6121b7a8dd4b7c7e9f2155b87 Mon Sep 17 00:00:00 2001 From: Bas Leijdekkers Date: Fri, 19 Aug 2011 15:25:54 +0200 Subject: [PATCH] naturalCompare for sorting in "natural/human" sort order --- .../openapi/util/text/StringUtil.java | 54 ++++++++++++++++++- .../intellij/util/text/StringUtilTest.java | 19 ++++++- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java index 3c89c2b801b8..776f513229a6 100644 --- a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java +++ b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2010 JetBrains s.r.o. + * Copyright 2000-2011 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1959,6 +1959,58 @@ public class StringUtil { return res; } + /** + * Implementation of "Sorting for Humans: Natural Sort Order": + * http://www.codinghorror.com/blog/2007/12/sorting-for-humans-natural-sort-order.html + */ + public static int naturalCompare(String string1, String string2) { + return naturalCompare(string1, string2, false); + } + + private static int naturalCompare(String string1, String string2, boolean caseSensitive) { + final int string1Length = string1.length(); + final int string2Length = string2.length(); + for(int i = 0, j = 0; i < string1Length && j < string2Length; i++, j++) { + final char ch1 = string1.charAt(i); + final char ch2 = string2.charAt(j); + if (isDigit(ch1) && isDigit(ch2)) { + int startNum1 = i; + int startNum2 = j; + i++; + j++; + while (i < string1Length && isDigit(string1.charAt(i))) i++; + while (j < string2Length && isDigit(string2.charAt(j))) j++; + final int difference = Integer.parseInt(string1.substring(startNum1, i)) - + Integer.parseInt(string2.substring(startNum2, j)); + if (difference != 0) { + return difference; + } + } else { + if (caseSensitive) { + return ch1 - ch2; + } else { + // similar logic to charsMatch() below + final int diff1 = Character.toUpperCase(ch1) - Character.toUpperCase(ch2); + if (diff1 != 0) { + final int diff2 = Character.toLowerCase(ch1) - Character.toLowerCase(ch2); + if (diff2 != 0) { + return diff1; + } + } + } + } + } + if (!caseSensitive && string1Length == string2Length) { + // do case sensitive compare if case insensitive strings are equal + return naturalCompare(string1, string2, true); + } + return string1Length - string2Length; + } + + public static boolean isDigit(char c) { + return c >= '0' && c <= '9'; + } + public static int compare(@Nullable String s1, @Nullable String s2, boolean ignoreCase) { if (s1 == s2) return 0; if (s1 == null) return -1; diff --git a/platform/util/testSrc/com/intellij/util/text/StringUtilTest.java b/platform/util/testSrc/com/intellij/util/text/StringUtilTest.java index 0a529a75da2f..f6cef2aa0101 100644 --- a/platform/util/testSrc/com/intellij/util/text/StringUtilTest.java +++ b/platform/util/testSrc/com/intellij/util/text/StringUtilTest.java @@ -18,7 +18,7 @@ package com.intellij.util.text; import com.intellij.openapi.util.text.StringUtil; import junit.framework.TestCase; -import java.util.List; +import java.util.*; /** * @author Eugene Zhuravlev @@ -70,4 +70,21 @@ public class StringUtilTest extends TestCase { assertFalse(StringUtil.startsWithConcatenationOf("something", "something", ".")); assertFalse(StringUtil.startsWithConcatenationOf("some", "something", "")); } + + public void testNaturalCompare() { + final List strings = new ArrayList(Arrays.asList("Test99", "tes0", "test0", "testing", "test", "test99", "test011", "test1", + "test 3", "test2", "test10a", "test10", "1.2.10.5", "1.2.9.1")); + final Comparator c = new Comparator() { + @Override + public int compare(String o1, String o2) { + return StringUtil.naturalCompare(o1, o2); + } + }; + Collections.sort(strings, c); + assertEquals(Arrays.asList("1.2.9.1", "1.2.10.5", "tes0", "test", "test 3", "test0", "test1", "test2", "test10", "test10a", + "test011", "Test99", "test99", "testing"), strings); + final List strings2 = new ArrayList(Arrays.asList("t1", "T2", "T1", "t2")); + Collections.sort(strings2, c); + assertEquals(Arrays.asList("T1", "t1", "T2", "t2"), strings2); + } }