001package org.unix4j.util.sort; 002 003import org.unix4j.util.StringUtil; 004 005import java.util.Comparator; 006import java.util.Locale; 007import java.util.Objects; 008 009/** 010 * A comparator for decimal strings sorted numerically, first by numeric sign; then by SI suffix 011 * (either empty, or 'k' or 'K', or one of 'MGTPEZY', in that order); and finally by numeric value. 012 * For example, '1023M' sorts before '1G' because 'M' (mega) precedes 'G' (giga) as an SI suffix. 013 * <p> 014 * The syntax for numbers is the same as for {@link DecimalNumberStringComparator}; the SI suffix must 015 * immediately follow the number. 016 */ 017public class UnitsNumberStringComparator implements Comparator<CharSequence> { 018 019 private static final String SI_UNITS = "KMGTPEZY"; 020 021 /** 022 * The instance for the default locale returned by {@link #getInstance()}. 023 */ 024 private static final UnitsNumberStringComparator DEFAULT_INSTANCE = new UnitsNumberStringComparator(); 025 026 /** 027 * Returns the instance for the default locale. 028 * 029 * @see Locale#getDefault() 030 */ 031 public static UnitsNumberStringComparator getInstance() { 032 return DEFAULT_INSTANCE; 033 } 034 035 /** 036 * Returns an instance for the specified locale. 037 */ 038 public static UnitsNumberStringComparator getInstance(Locale locale) { 039 return new UnitsNumberStringComparator(locale); 040 } 041 042 private final DecimalNumberStringComparator numberComparator; 043 044 /** 045 * Private constructor used to create the {@link #DEFAULT_INSTANCE}. 046 */ 047 private UnitsNumberStringComparator() { 048 this(DecimalNumberStringComparator.getInstance()); 049 } 050 051 /** 052 * Private constructor used by {@link #getInstance(Locale)}. 053 */ 054 private UnitsNumberStringComparator(Locale locale) { 055 this(DecimalNumberStringComparator.getInstance(locale)); 056 } 057 058 /** 059 * Constructor with decimal number comparator. 060 * 061 * @param numberComparator 062 * the decimal number comparator 063 */ 064 public UnitsNumberStringComparator(DecimalNumberStringComparator numberComparator) { 065 this.numberComparator = Objects.requireNonNull(numberComparator); 066 } 067 068 @Override 069 public int compare(CharSequence s1, CharSequence s2) { 070 final int start1 = StringUtil.findStartTrimWhitespace(s1); 071 final int end1 = StringUtil.findEndTrimWhitespace(s1); 072 final int start2 = StringUtil.findStartTrimWhitespace(s2); 073 final int end2 = StringUtil.findEndTrimWhitespace(s2); 074 final boolean isNeg1 = start1 < end1 && s1.charAt(start1) == '-'; 075 final boolean isNeg2 = start2 < end2 && s2.charAt(start2) == '-'; 076 final int si1 = siUnit(s1, start1, end1); 077 final int si2 = siUnit(s2, start2, end2); 078 final int e1 = si1 < 0 ? end1 : end1 - 1; 079 final int e2 = si2 < 0 ? end2 : end2 - 1; 080 final int cmp = numberComparator.compare(s1, start1, e1, s2, start2, e2); 081 if (isNeg1 != isNeg2) { 082 return cmp; 083 } 084 if (si1 < si2) { 085 return isNeg1 ? 1 : -1; 086 } 087 if (si1 > si2) { 088 return isNeg1 ? -1 : 1; 089 } 090 return cmp; 091 } 092 093 private static final int siUnit(CharSequence s, int start, int end) { 094 if (start < end) { 095 final int ch = s.charAt(end - 1); 096 final int si = SI_UNITS.indexOf(ch); 097 if (si >= 0) { 098 return si; 099 } 100 if (ch == 'k') return 0;//lower case only for k 101 } 102 return -1; 103 } 104}