/* * This file is part of muCommander, http://www.mucommander.com * Copyright (C) 2002-2016 Maxence Bernard * * muCommander 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 3 of the License, or * (at your option) any later version. * * muCommander 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 program. If not, see <http://www.gnu.org/licenses/>. */ package com.mucommander.commons.util; import java.text.Collator; import java.util.Locale; /** * This class contains convenience methods for working with strings. * @author Maxence Bernard, Nicolas Rinaudo */ public final class StringUtils { public static final String EMPTY = ""; /** * Prevents instantiation of this class. */ private StringUtils() { } /** * Returns <code>true</code> if <code>a</code> ends with <code>b</code> regardless of the case. * <p> * This method has a known bug under some alphabets with peculiar capitalisation rules such as the Georgian one, * where <code>Character.toUpperCase(a) == Character.toUpperCase(b)</code> doesn't necessarily imply that * <code>Character.toLowerCase(a) == Character.toLowerCase(b)</code>. The performance hit of testing for this * exceptions is so huge that it was deemed an acceptable issue. * </p> * <p> * Note that this method will return <code>true</code> if <code>b</code> is an emptry string. * </p> * @param a string to test. * @param b suffix to test for. * @return <code>true</code> if <code>a</code> ends with <code>b</code> regardless of the case, <code>false</code> otherwise. */ public static boolean endsWithIgnoreCase(String a, String b) { return matchesIgnoreCase(a, b, a.length()); } /** * Returns <code>true</code> if the substring of <code>a</code> starting at <code>posA</code> matches <code>b</code> regardless of the case. * <p> * This method has a known bug under some alphabets with peculiar capitalisation rules such as the Georgian one, * where <code>Character.toUpperCase(a) == Character.toUpperCase(b)</code> doesn't necessarily imply that * <code>Character.toLowerCase(a) == Character.toLowerCase(b)</code>. The performance hit of testing for this * exceptions is so huge that it was deemed an acceptable issue. * </p> * <p> * Note that this method will return <code>true</code> if <code>b</code> is an emptry string. * </p> * @param a string to test. * @param b suffix to test for. * @param posA position in <code>a</code> at which to look for <code>b</code> * @return <code>true</code> if <code>a</code> ends with <code>b</code> regardless of the case, <code>false</code> otherwise. * @throws ArrayIndexOutOfBoundsException if <code>a.length</code> is smaller than <code>posA</code>. */ public static boolean matchesIgnoreCase(String a, String b, int posA) { int posB; // Position in b. char cA; // Current character in a. char cB; // Current character in b. // Checks whether there's any point in testing the strings. if(posA < (posB = b.length())) return false; // Loops until we've tested the whole of b. while(posB > 0) { // Works on lower-case characters only. if(!Character.isLowerCase(cA = a.charAt(--posA))) cA = Character.toLowerCase(cA); if(!Character.isLowerCase(cB = b.charAt(--posB))) cB = Character.toLowerCase(cB); if(cA != cB) return false; } return true; } /** * Returns <code>true</code> if <code>a</code> ends with <code>b</code> regardless of the case. * <p> * This method has a known bug under some alphabets with peculiar capitalisation rules such as the Georgian one, * where <code>Character.toUpperCase(a) == Character.toUpperCase(b)</code> doesn't necessarily imply that * <code>Character.toLowerCase(a) == Character.toLowerCase(b)</code>. The performance hit of testing for this * exceptions is so huge that it was deemed an acceptable issue. * </p> * <p> * Note that this method will return <code>true</code> if <code>b</code> is an emptry string. * </p> * @param a string to test. * @param b suffix to test for. * @return <code>true</code> if <code>a</code> ends with <code>b</code> regardless of the case, <code>false</code> otherwise. */ public static boolean endsWithIgnoreCase(String a, char[] b) {return matchesIgnoreCase(a, b, a.length());} /** * Returns <code>true</code> if the substring of <code>a</code> starting at <code>posA</code> matches <code>b</code> regardless of the case. * <p> * This method has a known bug under some alphabets with peculiar capitalisation rules such as the Georgian one, * where <code>Character.toUpperCase(a) == Character.toUpperCase(b)</code> doesn't necessarily imply that * <code>Character.toLowerCase(a) == Character.toLowerCase(b)</code>. The performance hit of testing for this * exceptions is so huge that it was deemed an acceptable issue. * </p> * <p> * Note that this method will return <code>true</code> if <code>b</code> is an emptry string. * </p> * @param a string to test. * @param b suffix to test for. * @param posA position in <code>a</code> at which to look for <code>b</code> * @return <code>true</code> if <code>a</code> ends with <code>b</code> regardless of the case, <code>false</code> otherwise. * @throws ArrayIndexOutOfBoundsException if <code>a.length</code> is smaller than <code>posA</code>. */ public static boolean matchesIgnoreCase(String a, char[] b, int posA) { int posB; // Position in b. char cA; // Current character in a. char cB; // Current character in b. // Checks whether there's any point in testing the strings. if(posA < (posB = b.length)) return false; while(posB > 0) { if(!Character.isLowerCase(cA = a.charAt(--posA))) cA = Character.toLowerCase(cA); if(!Character.isLowerCase(cB = b[--posB])) cB = Character.toLowerCase(cB); if(cA != cB) return false; } return true; } /** * Equivalent of <code>String.endsWith(String)</code> using a <code>char[]</code>. * @param a String to test. * @param b suffix to test. * @return <code>true</code> if <code>a</code> ends with <code>b</code>. */ public static boolean endsWith(String a, char[] b) { return matches(a, b, a.length()); } /** * Returns <code>true</code> if the substring of <code>a</code> ending at <code>posA</code> matches <code>b</code>. * @param a String to test. * @param b substring to look for. * @param posA position in <code>a</code> at which to look for <code>b</code> * @return <code>true</code> if <code>a</code> contains <code>b</code> at position <code>posA - b.length()</code>, <code>false</code> otherwise.. */ public static boolean matches(String a, char[] b, int posA) { int posB; if(posA < (posB = b.length)) return false; while(posB > 0) if(a.charAt(--posA) != b[--posB]) return false; return true; } /** * Returns <code>true</code> if <code>a</code> starts with <code>b</code> regardless of the case. * <p> * Note that this method will return <code>true</code> if <code>b</code> is an emptry string. * </p> * @param a string to test. * @param b prefix to test for. * @return <code>true</code> if <code>a</code> starts with <code>b</code> regardless of the case, <code>false</code> otherwise.. */ public static boolean startsWithIgnoreCase(String a, String b) { return a.regionMatches(true, 0, b, 0, b.length()); } /** * This method is a locale-aware version of <code>java.lang.String#equals(Object)</code>. It returns * <code>true</code> if the two given <code>String</code> are equal in the specified <code>Locale</code>. * * <p>This method is useful for testing text expressed in a language where two strings with an identical * written representation can have a different <code>String</code> representation according to * <code>java.lang.String#equals(Object)</code>. Japanese is such a language for instance. * This method uses the <code>java.text.Collator</code> class under the hood. * * @param s1 a String to compare * @param s2 a String to compare * @param locale the Locale to consider for testing the String * @return true if the two given String are equal in the specified Locale */ public static boolean equals(String s1, String s2, Locale locale) { return Collator.getInstance(locale).equals(s1, s2); } /** * Compares the two specified strings and returns <code>true</code> if both strings are equal. This method handles * <code>null</code> values with no risk of a <code>NullPointerException</code>. The comparison is case-sensitive * only if requested. * * <p>In other words, this method returns <code>true</code> if strings are either both <code>null</code> * or equal according to <code>String#equals(String)</code> for case-sensitive comparison, or * <code>String#equalsIgnoreCase(String)</code> for case-insensitive comparison.</p> * * @param s1 string to compare, potentially <code>null</code> * @param s2 string to compare, potentially <code>null</code> * @param caseSensitive <code>true</code> for case-sensitive comparison, <code>false</code> for case-insensitive comparison * @return <code>true</code> if strings are equal or both null */ public static boolean equals(String s1, String s2, boolean caseSensitive) { if(s1 == null && s2 == null) return true; if(caseSensitive) return s1 != null && s1.equals(s2); return s1 != null && s1.equalsIgnoreCase(s2); } /** * Parses the string argument as a signed decimal integer. If the string cannot be * parsed a default value is returned. * @param s a String containing the int representation to be parsed * @param def a default value if string cannot be parsed * @return the integer value represented by the argument or default value if it cannot be parsed */ public static int parseIntDef(String s, int def) { try { return Integer.parseInt(s); } catch (NumberFormatException e) { return def; } } /** * Capitalizes the given string, making its first character upper case, and the rest of them lower case. * This method reeturns an empty string if <code>null</code> or an empty string is passed. * * @param s the string to capitalize * @return the capitalized string */ public static String capitalize(String s) { if(isNullOrEmpty(s)) return EMPTY; StringBuilder out; out = new StringBuilder(s.length()); out.append(Character.toUpperCase(s.charAt(0))); if(s.length() > 1) out.append(s.substring(1).toLowerCase()); return out.toString(); } /** * Shorthand for {@link #flatten(String[], String)} invoked with a <code>" "</code> separator. * * @param s the string array to flatten * @return the flattened string array */ public static String flatten(String s[]) { return flatten(s, " "); } /** * Concatenates all of the given string array's elements into a single string, separating each element * by the specified separator string. <code>null</code> and empty string values are simply skipped and not * reflected in the returned string. If the string array is <code>null</code>, the returned string will also be * <code>null</code>. * * @param s the string array to flatten * @param separator the String that separates each * @return the flattened string array */ public static String flatten(String s[], String separator) { if(s==null) return null; StringBuilder sb = new StringBuilder(); int sLen = s.length; boolean first = true; String el; for(int i=0; i<sLen; i++) { el = s[i]; if (isNullOrEmpty(el)) continue; if(first) first = false; else sb.append(separator); sb.append(el); } return sb.toString(); } /** * Returns true if the given string is null or empty (i.e. it's length is 0) * * @param string - the given String to check * @return true if the given string is null or empty, false otherwise */ public static boolean isNullOrEmpty(String string) { return string == null || string.isEmpty(); } }