/* * Copyright 2010-2015 Institut Pasteur. * * This file is part of Icy. * * Icy is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Icy 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Icy. If not, see <http://www.gnu.org/licenses/>. */ package icy.util; import icy.math.MathUtil; import java.util.Comparator; /** * @author stephane */ public class StringUtil { /* * 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 */ /** * This is an updated version of Alphanum Algorithm Comparator * with enhancements made by Daniel Migowski, Andre Bogus, and David Koelle */ public static class AlphanumComparator implements Comparator<String> { /** Length of string is passed in for improved efficiency (only need to calculate it once) **/ private final String getChunk(String s, int slength, int index) { int marker = index; 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) { 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 = 0; 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 result = thisChunk.compareTo(thatChunk); if (result != 0) return result; } return s1Length - s2Length; } } /** * Return defaultValue if value is empty */ public static String getValue(String value, String defaultValue) { if (StringUtil.isEmpty(value)) return defaultValue; return value; } /** * Returns the next number found from specified <code>startIndex</code> in specified string.<br> * Returns an empty string if no number was found. */ public static CharSequence getNextNumber(CharSequence text, int index) { final int len = text.length(); // get starting digit char index final int st = getNextDigitCharIndex(text, index); // we find a digit char ? if (st >= 0) { // get ending digit char index int end = StringUtil.getNextNonDigitCharIndex(text, st); if (end < 0) end = len; // get value return text.subSequence(st, end); } return ""; } /** * Return the index of previous digit char from specified index in specified string<br> * return -1 if not found */ public static int getPreviousDigitCharIndex(CharSequence value, int from) { final int len = value.length(); if (from >= len) return -1; int index = from; while (index >= 0) { if (Character.isDigit(value.charAt(index))) return index; index--; } return -1; } /** * Return the index of previous letter char from specified index in specified string<br> * return -1 if not found */ public static int getPreviousLetterCharIndex(CharSequence value, int from) { final int len = value.length(); if (from >= len) return -1; int index = from; while (index >= 0) { if (Character.isLetter(value.charAt(index))) return index; index--; } return -1; } /** * Return the index of previous non digit char from specified index in specified string<br> * return -1 if not found */ public static int getPreviousNonDigitCharIndex(CharSequence value, int from) { final int len = value.length(); if (from >= len) return -1; int index = from; while (index >= 0) { if (!Character.isDigit(value.charAt(index))) return index; index--; } return -1; } /** * Return the index of previous non letter char from specified index in specified string<br> * Return -1 if not found. */ public static int getPreviousNonLetterCharIndex(CharSequence value, int from) { final int len = value.length(); if (from >= len) return -1; int index = from; while (index >= 0) { if (!Character.isLetter(value.charAt(index))) return index; index--; } return -1; } /** * Return the index of next digit char from specified index in specified string<br> * return -1 if not found */ public static int getNextDigitCharIndex(CharSequence value, int from) { final int len = value.length(); if (from < 0) return -1; int index = from; while (index < len) { if (Character.isDigit(value.charAt(index))) return index; index++; } return -1; } /** * Return the index of next letter char from specified index in specified string<br> * return -1 if not found */ public static int getNextLetterCharIndex(CharSequence value, int from) { final int len = value.length(); if (from < 0) return -1; int index = from; while (index < len) { if (Character.isDigit(value.charAt(index))) return index; index++; } return -1; } /** * Return the index of next non digit char from specified index in specified string<br> * return -1 if not found */ public static int getNextNonDigitCharIndex(CharSequence value, int from) { final int len = value.length(); if (from < 0) return -1; int index = from; while (index < len) { if (!Character.isDigit(value.charAt(index))) return index; index++; } return -1; } /** * Return the index of next non letter char from specified index in specified string<br> * return -1 if not found */ public static int getNextNonLetterCharIndex(CharSequence value, int from) { final int len = value.length(); if (from < 0) return -1; int index = from; while (index < len) { if (!Character.isLetter(value.charAt(index))) return index; index++; } return -1; } /** * Return the index of next control char from specified <code>startIndex</code> in specified * string.<br> * return -1 if no control character found. */ public static int getNextCtrlCharIndex(CharSequence value, int startIndex) { final int len = value.length(); if (startIndex < 0) return -1; int index = startIndex; while (index < len) { if (Character.isISOControl(value.charAt(index))) return index; index++; } return -1; } /** * Limit the length of the specified string to maxlen. */ public static String limit(String value, int maxlen, boolean tailLimit) { if (value == null) return null; final int len = value.length(); if (len > maxlen) { // simple truncation if (tailLimit || (maxlen <= 8)) return value.substring(0, maxlen - 2).trim() + "..."; // cut center final int cut = (maxlen - 3) / 2; return value.substring(0, cut).trim() + "..." + value.substring(len - cut).trim(); } return value; } /** * Limit the length of the specified string to maxlen. */ public static String limit(String value, int maxlen) { return limit(value, maxlen, false); } /** * Truncate the text to a specific size, according a keyword.<br> * The text will be truncated around the place where the keyword is found.<br> * If the string is found at the beginning, the text will be like this:<br/> * <b><center>Lorem ipsum dolor sit amet, consec...</center><b/> * * @param fullText * : text to be truncated. * @param keyword * : string to be found in the text and truncated around. * @param maxSize * : max size of the string */ public static String trunc(String fullText, String keyword, int maxSize) { int idx = fullText.toLowerCase().indexOf(keyword.toLowerCase()); // key not found if (idx == -1) return ""; String toReturn = fullText; int fullTextSize = fullText.length(); if (fullTextSize > maxSize) { int firstSpaceAfter; String textBeforeWord; int lastSpaceBefore; // extract the full word from the text firstSpaceAfter = fullText.indexOf(' ', idx); firstSpaceAfter = firstSpaceAfter == -1 ? fullTextSize : firstSpaceAfter; textBeforeWord = fullText.substring(0, idx); lastSpaceBefore = textBeforeWord.lastIndexOf(' '); lastSpaceBefore = lastSpaceBefore == -1 ? 0 : lastSpaceBefore; // determine if we are at the beginning, the end, or at the middle if (idx <= maxSize / 2) { toReturn = fullText.substring(0, maxSize); toReturn = toReturn.trim() + "..."; } else if ((fullTextSize - idx) <= maxSize / 2) { toReturn = fullText.substring(fullTextSize - maxSize, fullTextSize); toReturn = "..." + toReturn.trim(); } else { int beginIndex = idx - maxSize / 2; int endIndex = idx + maxSize / 2; if (endIndex > fullTextSize) System.out.println(endIndex); // beginIndex = beginIndex < 0 ? 0 : beginIndex; // endIndex = endIndex > fullTextSize ? fullTextSize : endIndex; toReturn = "..." + fullText.substring(beginIndex, endIndex).trim() + "..."; } } return toReturn; } /** * Return true if the specified String are exactly the same. * * @param trim * if true then string are trimmed before comparison */ public static boolean equals(String s1, String s2, boolean trim) { if (isEmpty(s1, trim)) return isEmpty(s2, trim); else if (isEmpty(s2, trim)) return false; if (trim) return s1.trim().equals(s2.trim()); return s1.equals(s2); } /** * Return true if the specified String are exactly the same */ public static boolean equals(String s1, String s2) { return equals(s1, s2, false); } /** * Return true if the specified String is empty. * * @param trim * trim the String before doing the empty test */ public static boolean isEmpty(String value, boolean trim) { if (value != null) { if (trim) return value.trim().length() == 0; return value.length() == 0; } return true; } /** * Return true if the specified String is empty. * The String is trimed by default before doing the test */ public static boolean isEmpty(String value) { return isEmpty(value, true); } /** * Try to parse a boolean from the specified String and return it. * Return 'def' is we can't parse any boolean from the string. */ public static boolean parseBoolean(String s, boolean def) { if (s == null) return def; final String value = s.toLowerCase(); if (value.equals(Boolean.toString(true))) return true; if (value.equals(Boolean.toString(false))) return false; return def; } /** * Try to parse a integer from the specified String and return it. * Return 'def' is we can't parse any integer from the string. */ public static int parseInt(String s, int def) { try { return Integer.parseInt(s); } catch (NumberFormatException E) { return def; } } /** * Try to parse a long integer from the specified String and return it. * Return 'def' is we can't parse any integer from the string. */ public static long parseLong(String s, long def) { try { return Long.parseLong(s); } catch (NumberFormatException E) { return def; } } /** * Try to parse a float from the specified String and return it. * Return 'def' is we can't parse any float from the string. */ public static float parseFloat(String s, float def) { try { return Float.parseFloat(s); } catch (NumberFormatException E) { return def; } } /** * Try to parse a double from the specified String and return it. * Return 'def' is we can't parse any double from the string. */ public static double parseDouble(String s, double def) { try { return Double.parseDouble(s); } catch (NumberFormatException E) { return def; } } /** * Try to parse a array of byte from the specified String and return it. * Return 'def' is we can't parse any array of byte from the string. */ public static byte[] parseBytes(String s, byte[] def) { if (s == null) return def; return s.getBytes(); } /** * Returns a <tt>String</tt> object representing the specified * boolean. If the specified boolean is <code>true</code>, then * the string {@code "true"} will be returned, otherwise the * string {@code "false"} will be returned. */ public static String toString(boolean value) { return Boolean.toString(value); } /** * Returns a <code>String</code> object representing the specified integer. */ public static String toString(int value) { return Integer.toString(value); } /** * Returns a <code>String</code> object representing the specified integer.<br> * If the returned String is shorter than specified length<br> * then leading '0' are added to the string. */ public static String toString(int value, int minSize) { String result = Integer.toString(value); while (result.length() < minSize) result = "0" + result; return result; } /** * Returns a <code>String</code> object representing the specified <code>long</code>. */ public static String toString(long value) { return Long.toString(value); } /** * Returns a string representation of the <code>float</code> argument. */ public static String toString(float value) { return Float.toString(value); } /** * Returns a string representation of the <code>double</code> argument. */ public static String toString(double value) { final int i = (int) value; if (i == value) return toString(i); return Double.toString(value); } /** * Returns a string representation of the <code>double</code> argument * with specified number of decimal. */ public static String toString(double value, int numDecimal) { return Double.toString(MathUtil.round(value, numDecimal)); } /** * Returns a string representation of the <code>double</code> argument with specified size :<br> * <code>toString(1.23456, 5)</code> --> <code>"1.2345"</code><br> * <code>toString(123.4567, 4)</code> --> <code>"123.4"</code><br> * <code>toString(1234.567, 2)</code> --> <code>"1234"</code> as we never trunk integer part.<br> * <code>toString(1234.5, 10)</code> --> <code>"1234.5"</code> as we never trunk integer part.<br> */ public static String toStringEx(double value, int size) { final int i = (int) value; if (i == value) return toString(i); return Double.toString(MathUtil.roundSignificant(value, size, true)); } /** * Return a string representation of the byte array argument. */ public static String toString(byte[] value) { return new String(value); } /** * Returns a string representation of the integer argument as an * unsigned integer in base 16. */ public static String toHexaString(int value) { return Integer.toHexString(value); } /** * Returns a string representation of the integer argument as an * unsigned integer in base 16.<br> * Force the returned string to have the specified size :<br> * If the string is longer then only last past is kept.<br> * If the string is shorter then leading 0 are added to the string. */ public static String toHexaString(int value, int size) { String result = Integer.toHexString(value); if (result.length() > size) return result.substring(result.length() - size); while (result.length() < size) result = "0" + result; return result; } /** * Remove <code>count</code> characters from the end of specified string. */ public static String removeLast(String value, int count) { if (value == null) return null; final int l = value.length(); if (l < 2) return ""; return value.substring(0, l - count); } /** * Creates a flattened version of the provided String. The flattening operation splits the * string by inserting spaces between words starting with an upper case letter, and converts * upper case letters to lower case (with the exception of the first word). Note that * <b>consecutive upper case letters will remain grouped</b>, as they are considered to * represent an acronym.<br/> * <br/> * <u>NOTE:</u> This method is optimized for class names that follow the Java naming convention. <br/> * Examples:<br/> * MyGreatClass -> "My great class"<br/> * MyXYZClass -> "My XYZ class" * * @param string * the string to flatten * @return a flattened (i.e. pretty-printed) String based on the name of the string */ public static String getFlattened(String string) { String[] words = string.split("(?=[A-Z])"); String output = words[0]; if (words.length > 1) { // words[0] is always empty here output = words[1]; for (int i = 2; i < words.length; i++) { String word = words[i]; if (word.length() == 1) { // single letter if (words[i - 1].length() == 1) { // append to the previous letter (acronym) output += word; } else { // new isolated letter or acronym output += " " + word; } } else output += " " + word.toLowerCase(); } } return output; } /** * Replace all C line break sequence : <code>"\n", "\r", "\r\n"</code><br> * from the specified <code>text</code> by <code>str</code>. */ public static String replaceCR(String text, String str) { return text.replaceAll("(\r\n|\n\r|\r|\n)", str); } /** * Remove all C line break sequence : <code>"\n", "\r", "\r\n"</code><br> * from the specified text. */ public static String removeCR(String text) { return replaceCR(text, ""); } /** * Convert the C line break sequence : <code>"\n", "\r", "\r\n"</code><br> * to HTML line break sequence. */ public static String toHtmlCR(String text) { return replaceCR(text, "<br>").replaceAll("(<BR>|<br/>|<BR/>)", "<br>"); } /** * Return true if the specified text contains HTML line break sequence. */ public static boolean containHtmlCR(String text) { return (text.indexOf("<br>") != -1) || (text.indexOf("<BR>") != -1) || (text.indexOf("<br/>") != -1) || (text.indexOf("<BR/>") != -1); } /** * Bold (inserting HTML bold tag) the specified keyword in the text. */ public static String htmlBoldSubstring(String text, String keyword, boolean ignoreCase) { // right now we just ignore 'b' keyword with produce error because of the <b> sequence. if (!isEmpty(text) && !isEmpty(keyword) && !keyword.toLowerCase().equals("b")) { final int keywordLen = keyword.length(); final String key; if (ignoreCase) key = keyword.toLowerCase(); else key = keyword; String result = text; int index; if (ignoreCase) index = result.toLowerCase().indexOf(key); else index = result.indexOf(key); while (index != -1) { result = result.substring(0, index) + "<b>" + result.substring(index, index + keywordLen) + "</b>" + result.substring(index + keywordLen); if (ignoreCase) index = result.toLowerCase().indexOf(key, index + keywordLen + 6); else index = result.indexOf(key, index + keywordLen + 6); } return result; } return text; } /** * Converts wildcard to regular expression. * * @param wildcard * @return regex */ public static String wildcardToRegex(String wildcard) { final StringBuffer s = new StringBuffer(wildcard.length()); s.append('^'); for (int i = 0, is = wildcard.length(); i < is; i++) { char c = wildcard.charAt(i); switch (c) { case '*': s.append(".*"); break; case '?': s.append("."); break; case '(': case ')': case '[': case ']': case '$': case '^': case '.': case '{': case '}': case '|': case '\\': s.append("\\"); s.append(c); break; default: s.append(c); break; } } s.append('$'); return (s.toString()); } }