// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.tools;
/*
* The Alphanum Algorithm is an improved sorting algorithm for strings
* containing numbers. Instead of sorting numbers in ASCII order like a standard
* sort, this algorithm sorts numbers in numeric order.
*
* The Alphanum Algorithm is discussed at http://www.DaveKoelle.com
*
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this library; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
import java.io.Serializable;
import java.text.Collator;
import java.util.Comparator;
import java.util.Locale;
/**
* The Alphanum Algorithm is an improved sorting algorithm for strings
* containing numbers: Instead of sorting numbers in ASCII order like a standard
* sort, this algorithm sorts numbers in numeric order.
*
* The Alphanum Algorithm is discussed at http://www.DaveKoelle.com
*
* This is an updated version with enhancements made by Daniel Migowski, Andre
* Bogus, and David Koelle and others.
*
*/
public final class AlphanumComparator implements Comparator<String>, Serializable {
private static final long serialVersionUID = 1L;
private static final AlphanumComparator INSTANCE = new AlphanumComparator();
/**
* Replies the unique instance.
* @return the unique instance
*/
public static AlphanumComparator getInstance() {
return INSTANCE;
}
/**
* Constructs a new Alphanum Comparator.
*/
private AlphanumComparator() {
}
/**
* Returns an alphanum chunk.
* Length of string is passed in for improved efficiency (only need to calculate it once).
* @param s string
* @param slength string length
* @param marker position
* @return alphanum chunk found at given position
*/
private static String getChunk(String s, int slength, int marker) {
StringBuilder chunk = new StringBuilder();
char c = s.charAt(marker);
chunk.append(c);
marker++;
if (Character.isDigit(c)) {
while (marker < slength) {
c = s.charAt(marker);
if (!Character.isDigit(c)) {
break;
}
chunk.append(c);
marker++;
}
} else {
while (marker < slength) {
c = s.charAt(marker);
if (Character.isDigit(c)) {
break;
}
chunk.append(c);
marker++;
}
}
return chunk.toString();
}
@Override
public int compare(String s1, String s2) {
if (s1 == null && s2 == null) {
return 0;
} else if (s1 == null) {
return -1;
} else if (s2 == null) {
return 1;
}
int thisMarker = 0;
int thatMarker = 0;
int s1Length = s1.length();
int s2Length = s2.length();
while (thisMarker < s1Length && thatMarker < s2Length) {
String thisChunk = getChunk(s1, s1Length, thisMarker);
thisMarker += thisChunk.length();
String thatChunk = getChunk(s2, s2Length, thatMarker);
thatMarker += thatChunk.length();
// If both chunks contain numeric characters, sort them numerically
int result;
if (Character.isDigit(thisChunk.charAt(0)) && Character.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;
}
}
}
} else {
// Instantiate the collator
Collator compareOperator = Collator.getInstance(Locale.getDefault());
// Compare regardless of accented letters
compareOperator.setStrength(Collator.SECONDARY);
result = compareOperator.compare(thisChunk, thatChunk);
}
if (result != 0) {
return result;
}
}
return s1Length - s2Length;
}
}