package org.ovirt.engine.core.common.businessentities.comparators; import java.io.Serializable; import java.math.BigInteger; import java.util.Comparator; /** * This class may be used to sort strings that have numeric sequences in them. <br> * <br> * A common problem is that sorting such strings lexicographically (as any other string) results in an order that may be * considered counter-intuitive, e.g. "Example10" will appear before "Example2" since '1' is lexicographically less than * '2'. <br> * <br> * The method compare() deals with these strings by splitting them into alternating subsequences of digits and * nondigits, sorting the digit sequences numerically and the nondigit sequences lexicographically. <br> * <br> * Nulls and empty strings are allowed; nulls are considered less than empty strings, and empty strings less than * nonempty ones. <br> * <br> * It is assumed that a string always begins with a nondigit sequence; if it actually begins with a digit sequence, the * behaviour is as if it started with an empty nondigit sequence. */ public class LexoNumericComparator implements Comparator<String>, Serializable { private boolean caseSensitive; public LexoNumericComparator(boolean caseSensitive) { this.caseSensitive = caseSensitive; } public LexoNumericComparator() { this(false); } @Override public int compare(String str1, String str2) { return comp(str1, str2, caseSensitive); } public static int comp(String str1, String str2) { return comp(str1, str2, false); } public static int comp(String str1, String str2, boolean caseSensitive) { if (str1 == null) { return (str2 == null) ? 0 : -1; } else if (str2 == null) { return 1; } boolean digitTurn = false; int begSeq1 = 0; int begSeq2 = 0; while (begSeq1 != str1.length()) { // str1 and str2 have the same prefix but str1 has an extra sequence => str1 > str2 if (begSeq2 == str2.length()) { return 1; } int endSeq1 = findEndOfSequence(str1, begSeq1, digitTurn); int endSeq2 = findEndOfSequence(str2, begSeq2, digitTurn); String seq1 = str1.substring(begSeq1, endSeq1); String seq2 = str2.substring(begSeq2, endSeq2); int compRes = compareSequence(seq1, seq2, digitTurn, caseSensitive); if (compRes != 0) { return compRes; } digitTurn = !digitTurn; begSeq1 = endSeq1; begSeq2 = endSeq2; } // str1 and str2 have the same prefix but str2 has an extra sequence => str1 < str2 if (begSeq2 != str2.length()) { return -1; } // if the comparison is case-insensitive, differentiate between the strings unless they're truly identical return Integer.signum(str1.compareTo(str2)); } private static int compareSequence(String seq1, String seq2, boolean digitSequence, boolean caseSensitive) { return digitSequence ? compDigitSequence(seq1, seq2, caseSensitive) : compNonDigitSequence(seq1, seq2, caseSensitive); } private static int compDigitSequence(String seq1, String seq2, boolean caseSensitive) { int compRes = new BigInteger(seq1).compareTo(new BigInteger(seq2)); return compRes == 0 ? compNonDigitSequence(seq1, seq2, caseSensitive) : compRes; } private static int compNonDigitSequence(String seq1, String seq2, boolean caseSensitive) { return Integer.signum(caseSensitive ? seq1.compareTo(seq2) : seq1.compareToIgnoreCase(seq2)); } private static int findEndOfSequence(String seq, int startIndex, boolean digitSequence) { return digitSequence ? findEndOfDigitSequence(seq, startIndex) : findEndOfNonDigitSequence(seq, startIndex); } private static int findEndOfDigitSequence(String seq, int startIndex) { for (int i = startIndex; i < seq.length(); ++i) { if (!Character.isDigit(seq.charAt(i))) { return i; } } return seq.length(); } private static int findEndOfNonDigitSequence(String seq, int startIndex) { for (int i = startIndex; i < seq.length(); ++i) { if (Character.isDigit(seq.charAt(i))) { return i; } } return seq.length(); } }