/* * To change this license header, choose License Headers in Project Properties. * To change this template file, choose Tools | Templates * and open the template in the editor. */ package edu.harvard.iq.dataverse.util; import java.io.Serializable; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Comparator; import java.util.List; /** * * * Compares String objects alphabetically, except if both Strings begin with a number, * then compares numerically. If one String begins with a number and the other doesn't, then * the number precedes the non-number. * * @author Ellen Kraffmiller * @author Leonid Andreev * * This comparator was originally created by Ellen Kraffmiller for the DVN v2. * Incorporated into DVN 4.0 by Leonid Andreev in Dec. 2013. * */ public class AlphaNumericComparator implements Comparator<String>, Serializable { public AlphaNumericComparator() { } @Override public int compare(String o1, String o2) { List tokenizedList1 = getTokenizedList(o1); List tokenizedList2 = getTokenizedList(o2); for (int i = 0; i < Math.min(tokenizedList1.size(), tokenizedList2.size()); i++) { Object token1 = tokenizedList1.get(i); Object token2 = tokenizedList2.get(i); if (token1 instanceof BigDecimal) { if (token2 instanceof BigDecimal) { int compareVal = ((BigDecimal) token1).compareTo((BigDecimal) token2); if (compareVal != 0) { return compareVal; } } else { return -1; // token1 is a number, token2 is not } } else if (token2 instanceof BigDecimal) { return 1; // token2 is a number, token1 is not } else { int compareVal = ((String) token1).compareTo((String) token2); if (compareVal != 0) { return compareVal; } } } // they match up, so compare based on who stll has tokens return new Integer(tokenizedList1.size()).compareTo(new Integer(tokenizedList2.size())); } /* this method returns a list of the String as tokens of BigDecimals and Strings // a slight challenge is to determine the intent of the user, e.g. a '-' could be for a negative number // or just be for demarcation (e.g A-1, A-2) // // some of the logic incorporated below: // a '.' is part of a number if if it is followed by a digit and there is only one instance // if there are multiple instances in a number (e.g. 1.5.2 the whole thing is treated as a String) // a ',' is part of a number if it is both preceded and followed by a digit (we do not validate that // it is in a proper grouping (i.e. 1,00 is treated as 100, even if that is not a standard way of writing it) // a '-' is only used to denote negative if it is the first character of the String */ private List getTokenizedList(String value) { List tokenizedList = new ArrayList(); char[] charArray = value.trim().toCharArray(); StringBuffer currentToken = new StringBuffer(""); boolean isCurrentTokenNumeric = false; for (int i = 0; i < charArray.length; i++) { char c = charArray[i]; boolean hasPrevChar = i > 0; boolean hasNextChar = i < charArray.length - 1; if (Character.isDigit(c) || (c == '.' && hasNextChar && Character.isDigit(charArray[i + 1])) || (c == ',' && hasNextChar && hasPrevChar && Character.isDigit(charArray[i - 1]) && Character.isDigit(charArray[i + 1])) || (c == '-' && !hasPrevChar && hasNextChar && Character.isDigit(charArray[i + 1]))) { if (!isCurrentTokenNumeric) { // reset tokenizedList.add(currentToken.toString()); currentToken = new StringBuffer(""); } if (c != ',') { // if comma, don't append as it's just a visual separator currentToken.append(c); } isCurrentTokenNumeric = true; } else { if (isCurrentTokenNumeric) { // reset try { tokenizedList.add(new BigDecimal(currentToken.toString())); } catch (NumberFormatException nfe) { tokenizedList.add(currentToken.toString()); // something went wrong, but go ahead and add a a String } currentToken = new StringBuffer(""); } currentToken.append(c); isCurrentTokenNumeric = false; } } // add the last token to the list if (isCurrentTokenNumeric) { try { tokenizedList.add(new BigDecimal(currentToken.toString())); } catch (NumberFormatException nfe) { tokenizedList.add(currentToken.toString()); // something went wrong, but go ahead and add a a String } } else { tokenizedList.add(currentToken.toString()); } return tokenizedList; } @Override public boolean equals(Object obj) { return (obj instanceof AlphaNumericComparator); } @Override public int hashCode() { int hash = 3; return hash; } }