/*******************************************************************************
* Copyright (c) 2001, 2010 Mathew A. Nelson and Robocode contributors
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://robocode.sourceforge.net/license/epl-v10.html
*
* Contributors:
* Flemming N. Larsen
* - Initial implementation
*******************************************************************************/
package net.sf.robocode.util;
import java.util.Comparator;
import static java.lang.Character.isDigit;
/**
* This is a comparator used for comparing two alphanumeric strings.
* Normally strings are sorted one character at a time, meaning that e.g. "1.22" comes before "1.3".
* However, with alphanumeric sorting, "1.3" comes before "1.22", as 3 is lesser than 22.
* This comparator also others letter in this order "A", "a", "B", "b" instead of the normal character
* order "A", "B", "a", "b".
*
* <b>Example of usage:</b>
* <pre>
* String[] strings = new String[] { "1.22", "1.3", "A", "B", "a", "b" };
*
* Arrays.sort(strings, new AlphanumericComparator());
*
* for (String s : strings) {
* System.out.println(s);
* }
* </pre>
*
* @author Flemming N. Larsen
*/
public class AlphanumericComparator implements Comparator<String>, java.io.Serializable {
private static final long serialVersionUID = 1L;
/**
* Compares two alphanumeric strings.
*
* @param str1 the first string to be compared.
* @param str2 the second string to be compared.
* @return a negative integer, zero, or a positive integer as the first string is less than, equal to, or greater
* than the second.
*/
public int compare(final String str1, final String str2) {
// Checks against null strings
if (str1 == null) {
return (str2 == null) ? 0 : 1;
}
if (str2 == null) {
return -1;
}
if (str1.equals(str2)) {
return 0;
}
// Main loop where we keep read tokens from both input strings until the tokens differ
String tok1;
int result = 0;
for (int index = 0;; index += tok1.length()) {
tok1 = readToken(str1, index);
result = compareTokens(tok1, readToken(str2, index));
if (result != 0) {
return result;
}
}
}
/**
* Reads a token from the specified input string from a start index.
*
* @param input the input string.
* @param startIndex the start index of the token to read.
* @return the read token or null if there is no token could be read.
*/
private static String readToken(final String input, final int startIndex) {
// Return null if the input string is null or the startIndex is out of bounds
if (input == null || startIndex >= input.length()) {
return null;
}
int index = startIndex + 1;
// Check if the next token is numeric or not
if (isDigit(input.charAt(startIndex))) {
// Handle numeric token, which contains only digits in one continuous sequence
for (; index < input.length() && isDigit(input.charAt(index)); index++) {
;
}
} else {
// Handle all other tokens that does not contain any digits
for (; index < input.length() && !isDigit(input.charAt(index)); index++) {
;
}
}
// Return our token
return input.substring(startIndex, index);
}
/**
* Compares two tokens.
*
* @param tok1 the first token to be compared.
* @param tok2 the second token to be compared.
* @return a negative integer, zero, or a positive integer as the first token is less than, equal to, or greater
* than the second.
*/
private static int compareTokens(final String tok1, final String tok2) {
// Checks against null strings
if (tok1 == null) {
return (tok2 == null) ? 0 : 1;
}
if (tok2 == null) {
return -1;
}
// A numeric token has precedence over other tokens
if (isDigit(tok1.charAt(0))) {
if (isDigit(tok2.charAt(0))) {
// Two numeric tokens are compared as two long values
return (int) (Long.parseLong(tok1) - Long.parseLong(tok2));
}
return -1;
} else if (isDigit(tok2.charAt(0))) {
return 1;
}
// Compare two non-numeric tokens in alphabetic order.
// That is, we want "A", "a", "B", "b" instead of "A", "B", "a", "b"
int result = tok1.toUpperCase().compareTo(tok2.toUpperCase());
if (result != 0) {
return result;
}
return tok1.compareTo(tok2);
}
/* Example application where output must be:
"1.2"
"1.3"
"1.22A"
"1.22"
"1c"
"1h"
"1p"
"2.1.1"
"2.1a"
"2.10.2"
"2.10a.1"
"2.10"
" "
"ALPHA"
"ALpha"
"Alpha1"
"Alpha2"
"Alpha"
"alPHA"
"alpha1"
"alpha2"
"alpha"
"alpha"
"BETA"
"BEta"
"BeTa"
"bEtA"
"beTA"
"beta"
""
<null>
*/
public static void main(String... args) {
// Our unsorted strings that must be sorted
final String[] strings = {
null, "", " ", "alpha2", "alpha1", "bEtA", "BeTa", "alpha", "Alpha2", "Alpha1", "Alpha", "1.2", "1.22",
"1.22A", "1.3", "2.10a.1", "2.1.1", "2.10", "2.1a", "2.10.2", "1c", "1h", "1p", "alpha", "beta", "alPHA",
"beTA", "ALpha", "BEta", "ALPHA", "BETA"
};
java.util.Arrays.sort(strings, new AlphanumericComparator());
for (String s : strings) {
System.out.println(s == null ? "<null>" : '"' + s + '"');
}
}
}