/******************************************************************************* * Copyright (c) 2015 ARM Ltd and others. * 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://www.eclipse.org/legal/epl-v10.html * * Contributors: * ARM Ltd and ARM Germany GmbH - Initial API and implementation *******************************************************************************/ package com.arm.cmsis.pack.utils; import java.util.Comparator; /** * Class to compare strings containing decimal digits alpha-numerically. That is * in particular useful to compare version strings. * <p/> * The class can be used: * <ul> * <li> as comparator to sort collections (in descending order by default) * <li> to compare strings directly using <code>alnumCompare()</code> static functions * </ul> * <p/> * Groups of digits are converted into numbers for comparison, other characters * are compared in standard way. * <p> * In contrast, standard lexicographical string comparison treats digits as characters, for example: * <dl> * <dt>alpha-numeric comparison:</dt> * <dd>"10.1" > "2.1"</dd> * <dd>"2.01" == "2.1"</dd> * <dt>standard lexicographical comparison:</dt> * <dd>"10.1" < "2.1"</dd> * <dd>"2.01" < "2.1"</dd> * </dl> * </p> * */ public class AlnumComparator implements Comparator<String> { private boolean caseSensitive = false; private boolean descending = true; /** * Constructs default case insensitive comparator with descending sort order * */ public AlnumComparator() { } /** * Constructs case insensitive comparator * @param descending - sorting order: true - descending, false - acceding */ public AlnumComparator(boolean descending) { this.descending = descending; } /** * Constructs comparator * @param descending sorting order: true - descending, false - acceding * @param caseSensitive comparison */ public AlnumComparator(boolean descending, boolean caseSensitive) { this.descending = descending; this.caseSensitive = caseSensitive; } /** * Checks if this comparator is case sensitive * @return true if comparator is case sensitive */ public boolean isCaseSensitive() { return caseSensitive; } /** * Sets comparator' case sensitivity * @param caseSensitive true if comparator should be case sensitive, false otherwise */ public void setCaseSensitive(boolean caseSensitive) { this.caseSensitive = caseSensitive; } /** * Checks if comparator works in descending order * @return true if comparator works in descending order */ public boolean isDescending() { return descending; } /** * Sets comparator descending oder * @param descending true if comparator should work in descending order */ public void setDescending(boolean descending) { this.descending = descending; } @Override public int compare(String str1, String str2) { if (isDescending()) { return compare(str2, str1, isCaseSensitive()); } return compare(str1, str2, isCaseSensitive()); } /** * Compares two strings * @param str1 first string to compare * @param str2 second string to compare * @param cs case sensitive flag * @return * <dd><b>0</b> if str1 equals str2</dd> * <dd><b>>0</b> if str1 greater than str2</dd> * <dd><b><0</b> if str1 less than str2</dd> */ protected int compare(String str1, String str2, boolean cs) { return alnumCompare(str1, str2, isCaseSensitive()); } /** * Compares two strings alpha-numerically * @param str1 first string to compare * @param str2 second string to compare * @param cs case sensitive flag * @return * <dd><b>0</b> if str1 equals str2</dd> * <dd><b>1</b> if str1 greater than str2</dd> * <dd><b>-1</b> if str1 less than str2</dd> */ public static int alnumCompare(final String str1, final String str2, boolean cs) { // allow comparison of null and empty strings if (str1 == null || str1.isEmpty()) { if (str2 == null || str2.isEmpty()) { return 0; } return -1; } else if (str2 == null || str2.isEmpty()) { return 1; } int l1 = str1.length(); int l2 = str2.length(); int i1 = 0; int i2 = 0; while (i1 < l1 && i2 < l2) { char c1 = str1.charAt(i1); char c2 = str2.charAt(i2); if (Character.isDigit(c1) && Character.isDigit(c2)) { int digitBegin1 = i1; int digitBegin2 = i2; // skip digits while (i1 < l1 && Character.isDigit(str1.charAt(i1))) { i1++; } while (i2 < l2 && Character.isDigit(str2.charAt(i2))) { i2++; } // extract "digit" strings String s1 = str1.substring(digitBegin1, i1); String s2 = str2.substring(digitBegin2, i2); // convert to integers int val1 = 0; int val2 = 0; try{ val1 = Integer.decode(s1); val2 = Integer.decode(s2); } catch (NumberFormatException e) { return str1.compareTo(str2); } if (val1 > val2) { return 1; } else if (val1 < val2) { return -1; } } else { if (!cs) { c1 = Character.toUpperCase(c1); c2 = Character.toUpperCase(c2); } if ( (c1 == '*' && alnumCompareWildcardMatch(str1.substring(i1), str2.substring(i2))) || (c2 == '*' && alnumCompareWildcardMatch(str2.substring(i2), str1.substring(i1))) ) { return 0; } if (c1 > c2) { return 1; } else if (c1 < c2) { return -1; } i1++; i2++; } } return (l1 - i1) - (l2 - i2); } /** * Compares two strings alpha-numerically respecting case * @param str1 - first string to compare * @param str2 - second string to compare * @return str1 > str2 : 1 ; str1 < str2 : -1; str1 == str2 : 0 */ public static int alnumCompare(final String str1, final String str2) { return alnumCompare(str1, str2, true); } /** * Compares two strings alpha-numerically ignoring case * @param str1 - first string to compare * @param str2 - second string to compare * @return str1 > str2 : 1 ; str1 < str2 : -1; str1 == str2 : 0 */ public static int alnumCompareNoCase(final String str1, final String str2) { return alnumCompare(str1, str2, false); } /** * Check if a String matches a Regex Pattern * @param pattern - the regex pattern * @param string - the string to match * @return true if string matches pattern, false otherwise */ public static boolean alnumCompareWildcardMatch(String p, String s) { int m = s.length(), n = p.length(); int count = 0; for (int i = 0; i < n; i++) { if (p.charAt(i) == '*') { count++; } } if (count==0 && m != n) { return false; } else if (n - count > m) { return false; } boolean[] match = new boolean[m+1]; match[0] = true; for (int i = 0; i < m; i++) { match[i+1] = false; } for (int i = 0; i < n; i++) { if (p.charAt(i) == '*') { for (int j = 0; j < m; j++) { match[j+1] = match[j] || match[j+1]; } } else { for (int j = m-1; j >= 0; j--) { match[j+1] = (p.charAt(i) == '?' || p.charAt(i) == s.charAt(j)) && match[j]; } match[0] = false; } } return match[m]; } }