If anyone who don't understand what is "natural sort order", here is a good post talking about this problem:
http://www.codinghorror.com/blog/archives/001018.html
Obviously, both hibernate and hibernate shard are using ASCII order at the moment. It turns out the sorted results are not really human friendly. User could sort the result again after getting the results, which is unnecessary. In hibernate shard, we can acctually add an algorithm to make the sorted results in a natural sort order.
The algorithm I am using is from here:
http://www.davekoelle.com/alphanum.html
To use the algorithm, two classes need to be modified. First in
ExitOperationUtils.java, when the
getPropertyValue method called, we want it to return an Object instead of a Comparable object.
Code:
public static Object getPropertyValue(Object obj, String propertyName) {
try {
StringBuilder propertyPath = new StringBuilder();
for(int i=0; i < propertyName.length(); i++) {
String s = propertyName.substring(i,i+1);
if (i == 0 || propertyName.charAt(i-1) == '.') {
propertyPath.append(StringUtil.capitalize(s));
} else {
propertyPath.append(s);
}
}
String[] methods = ("get" + propertyPath.toString().replaceAll("\\.", ".get")).split("\\.");
Object root = obj;
for (String method : methods) {
Method m = findPotentiallyPrivateMethod(root.getClass(), method);
m.setAccessible(true);
root = m.invoke(root);
if (root == null) {
break;
}
}
return root;
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
Other methods don't need to be changed in this class.
Then we can use the algorithm to sort in
OrderExitOperation.java. Only the
apply method need to be changed:
Code:
public List<Object> apply(List<Object> results) {
List<Object> nonNullList = ExitOperationUtils.getNonNullList(results);
Comparator<Object> comparator = new Comparator<Object>() {
private final boolean isDigit(char ch)
{
return ch >= 48 && ch <= 57;
}
/** Length of string is passed in for improved efficiency (only need to calculate it once) **/
private final String getChunk(String s, int slength, int marker)
{
StringBuilder chunk = new StringBuilder();
char c = s.charAt(marker);
chunk.append(c);
marker++;
if (isDigit(c))
{
while (marker < slength)
{
c = s.charAt(marker);
if (!isDigit(c))
break;
chunk.append(c);
marker++;
}
} else
{
while (marker < slength)
{
c = s.charAt(marker);
if (isDigit(c))
break;
chunk.append(c);
marker++;
}
}
return chunk.toString();
}
public int compare(Object o1, Object o2) {
if (o1 == o2) {
return 0;
}
Object o1Value = ExitOperationUtils.getPropertyValue(o1, propertyName);
Object o2Value = ExitOperationUtils.getPropertyValue(o2, propertyName);
if (o1Value == null) {
return -1;
}
if( o1Value.getClass().getSimpleName().equals("String")&& o2Value.getClass().getSimpleName().equals("String")){
String v1 = (String)o1Value;
String v2 = (String)o2Value;
int thisMarker = 0;
int thatMarker = 0;
int s1Length = v1.length();
int s2Length = v2.length();
while (thisMarker < s1Length && thatMarker < s2Length)
{
String thisChunk = getChunk(v1, s1Length, thisMarker);
thisMarker += thisChunk.length();
String thatChunk = getChunk(v2, s2Length, thatMarker);
thatMarker += thatChunk.length();
// If both chunks contain numeric characters, sort them numerically
int result = 0;
if (isDigit(thisChunk.charAt(0)) && 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
{
result = thisChunk.toLowerCase().compareTo(thatChunk.toLowerCase());
}
if (result != 0)
return result;
}
return s1Length - s2Length;
}
Comparable<Object> v01 = (Comparable<Object>) o1Value;
Comparable<Object> v02 = (Comparable<Object>) o2Value;
return v01.compareTo(v02);
}
};
Collections.sort(nonNullList, comparator);
if (order.toString().endsWith("desc")) {
Collections.reverse(nonNullList);
}
return nonNullList;
}
In the code above, I ignored the case of the strings. You can check the ignoreCase attribute of the Order object to determine if you should ignore the case.
I suggest the Hibernate team to consider add this sort option to the whole project. Because as the post said, in most situations, people don't want the results to be sorted according to the ASCII, but human readable.