/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.ketayao.fensy.util; import java.util.ArrayList; import java.util.Iterator; import java.util.List; public class StringUtils { /** * Represents a failed index search. * @since 2.1 */ public static final int INDEX_NOT_FOUND = -1; /** * The empty String {@code ""}. * @since 2.0 */ public static final String EMPTY = ""; public static boolean equals(CharSequence cs1, CharSequence cs2) { return cs1 == null ? cs2 == null : cs1.equals(cs2); } /** * Tests if this string ends with the specified suffix. * * @param src String to test * @param subS suffix * * @return <code>true</code> if the character sequence represented by the argument is * a suffix of the character sequence represented by this object; * <code>false</code> otherwise. */ public static boolean endsWithIgnoreCase(String src, String subS) { if (src == null || subS == null) { return src == subS; } String sub = subS.toLowerCase(); int sublen = sub.length(); int j = 0; int i = src.length() - sublen; if (i < 0) { return false; } while (j < sublen) { char source = Character.toLowerCase(src.charAt(i)); if (sub.charAt(j) != source) { return false; } j++; i++; } return true; } /** * <p>Checks if a CharSequence is whitespace, empty ("") or null.</p> * * <pre> * StringUtils.isBlank(null) = true * StringUtils.isBlank("") = true * StringUtils.isBlank(" ") = true * StringUtils.isBlank("bob") = false * StringUtils.isBlank(" bob ") = false * </pre> * * @param cs the CharSequence to check, may be null * @return {@code true} if the CharSequence is null, empty or whitespace * @since 2.0 * @since 3.0 Changed signature from isBlank(String) to isBlank(CharSequence) */ public static boolean isBlank(final CharSequence cs) { int strLen; if (cs == null || (strLen = cs.length()) == 0) { return true; } for (int i = 0; i < strLen; i++) { if (Character.isWhitespace(cs.charAt(i)) == false) { return false; } } return true; } public static String[] split(final String str) { return split(str, null, -1); } /** * <p>Splits the provided text into an array, separators specified. * This is an alternative to using StringTokenizer.</p> * * <p>The separator is not included in the returned String array. * Adjacent separators are treated as one separator. * For more control over the split use the StrTokenizer class.</p> * * <p>A {@code null} input String returns {@code null}. * A {@code null} separatorChars splits on whitespace.</p> * * <pre> * StringUtils.split(null, *) = null * StringUtils.split("", *) = [] * StringUtils.split("abc def", null) = ["abc", "def"] * StringUtils.split("abc def", " ") = ["abc", "def"] * StringUtils.split("abc def", " ") = ["abc", "def"] * StringUtils.split("ab:cd:ef", ":") = ["ab", "cd", "ef"] * </pre> * * @param str the String to parse, may be null * @param separatorChars the characters used as the delimiters, * {@code null} splits on whitespace * @return an array of parsed Strings, {@code null} if null String input */ public static String[] split(final String str, final String separatorChars) { return splitWorker(str, separatorChars, -1, false); } public static String[] split(final String str, final String separatorChars, final int max) { return splitWorker(str, separatorChars, max, false); } /** * <p>Splits the provided text into an array, separator specified. * This is an alternative to using StringTokenizer.</p> * * <p>The separator is not included in the returned String array. * Adjacent separators are treated as one separator. * For more control over the split use the StrTokenizer class.</p> * * <p>A {@code null} input String returns {@code null}.</p> * * <pre> * StringUtils.split(null, *) = null * StringUtils.split("", *) = [] * StringUtils.split("a.b.c", '.') = ["a", "b", "c"] * StringUtils.split("a..b.c", '.') = ["a", "b", "c"] * StringUtils.split("a:b:c", '.') = ["a:b:c"] * StringUtils.split("a b c", ' ') = ["a", "b", "c"] * </pre> * * @param str the String to parse, may be null * @param separatorChar the character used as the delimiter * @return an array of parsed Strings, {@code null} if null String input * @since 2.0 */ public static String[] split(final String str, final char separatorChar) { return splitWorker(str, separatorChar, false); } /** * Performs the logic for the {@code split} and * {@code splitPreserveAllTokens} methods that do not return a * maximum array length. * * @param str the String to parse, may be {@code null} * @param separatorChar the separate character * @param preserveAllTokens if {@code true}, adjacent separators are * treated as empty token separators; if {@code false}, adjacent * separators are treated as one separator. * @return an array of parsed Strings, {@code null} if null String input */ private static String[] splitWorker(final String str, final char separatorChar, final boolean preserveAllTokens) { // Performance tuned for 2.0 (JDK1.4) if (str == null) { return null; } final int len = str.length(); if (len == 0) { return new String[0]; } final List<String> list = new ArrayList<String>(); int i = 0, start = 0; boolean match = false; boolean lastMatch = false; while (i < len) { if (str.charAt(i) == separatorChar) { if (match || preserveAllTokens) { list.add(str.substring(start, i)); match = false; lastMatch = true; } start = ++i; continue; } lastMatch = false; match = true; i++; } if (match || preserveAllTokens && lastMatch) { list.add(str.substring(start, i)); } return list.toArray(new String[list.size()]); } private static String[] splitWorker(final String str, final String separatorChars, final int max, final boolean preserveAllTokens) { // Performance tuned for 2.0 (JDK1.4) // Direct code is quicker than StringTokenizer. // Also, StringTokenizer uses isSpace() not isWhitespace() if (str == null) { return null; } final int len = str.length(); if (len == 0) { return new String[0]; } final List<String> list = new ArrayList<String>(); int sizePlus1 = 1; int i = 0, start = 0; boolean match = false; boolean lastMatch = false; if (separatorChars == null) { // Null separator means use whitespace while (i < len) { if (Character.isWhitespace(str.charAt(i))) { if (match || preserveAllTokens) { lastMatch = true; if (sizePlus1++ == max) { i = len; lastMatch = false; } list.add(str.substring(start, i)); match = false; } start = ++i; continue; } lastMatch = false; match = true; i++; } } else if (separatorChars.length() == 1) { // Optimise 1 character case final char sep = separatorChars.charAt(0); while (i < len) { if (str.charAt(i) == sep) { if (match || preserveAllTokens) { lastMatch = true; if (sizePlus1++ == max) { i = len; lastMatch = false; } list.add(str.substring(start, i)); match = false; } start = ++i; continue; } lastMatch = false; match = true; i++; } } else { // standard case while (i < len) { if (separatorChars.indexOf(str.charAt(i)) >= 0) { if (match || preserveAllTokens) { lastMatch = true; if (sizePlus1++ == max) { i = len; lastMatch = false; } list.add(str.substring(start, i)); match = false; } start = ++i; continue; } lastMatch = false; match = true; i++; } } if (match || preserveAllTokens && lastMatch) { list.add(str.substring(start, i)); } return list.toArray(new String[list.size()]); } /** * <p>Checks if a CharSequence is not empty (""), not null and not whitespace only.</p> * * <pre> * StringUtils.isNotBlank(null) = false * StringUtils.isNotBlank("") = false * StringUtils.isNotBlank(" ") = false * StringUtils.isNotBlank("bob") = true * StringUtils.isNotBlank(" bob ") = true * </pre> * * @param cs the CharSequence to check, may be null * @return {@code true} if the CharSequence is * not empty and not null and not whitespace * @since 2.0 * @since 3.0 Changed signature from isNotBlank(String) to isNotBlank(CharSequence) */ public static boolean isNotBlank(final CharSequence cs) { return !isBlank(cs); } public static boolean isEmpty(final CharSequence cs) { return cs == null || cs.length() == 0; } /** * <p>Gets the substring after the first occurrence of a separator. * The separator is not returned.</p> * * <p>A {@code null} string input will return {@code null}. * An empty ("") string input will return the empty string. * A {@code null} separator will return the empty string if the * input string is not {@code null}.</p> * * <p>If nothing is found, the empty string is returned.</p> * * <pre> * StringUtils.substringAfter(null, *) = null * StringUtils.substringAfter("", *) = "" * StringUtils.substringAfter(*, null) = "" * StringUtils.substringAfter("abc", "a") = "bc" * StringUtils.substringAfter("abcba", "b") = "cba" * StringUtils.substringAfter("abc", "c") = "" * StringUtils.substringAfter("abc", "d") = "" * StringUtils.substringAfter("abc", "") = "abc" * </pre> * * @param str the String to get a substring from, may be null * @param separator the String to search for, may be null * @return the substring after the first occurrence of the separator, * {@code null} if null String input * @since 2.0 */ public static String substringAfter(final String str, final String separator) { if (isEmpty(str)) { return str; } if (separator == null) { return EMPTY; } final int pos = str.indexOf(separator); if (pos == INDEX_NOT_FOUND) { return EMPTY; } return str.substring(pos + separator.length()); } /** * <p>Gets the substring before the last occurrence of a separator. * The separator is not returned.</p> * * <p>A {@code null} string input will return {@code null}. * An empty ("") string input will return the empty string. * An empty or {@code null} separator will return the input string.</p> * * <p>If nothing is found, the string input is returned.</p> * * <pre> * StringUtils.substringBeforeLast(null, *) = null * StringUtils.substringBeforeLast("", *) = "" * StringUtils.substringBeforeLast("abcba", "b") = "abc" * StringUtils.substringBeforeLast("abc", "c") = "ab" * StringUtils.substringBeforeLast("a", "a") = "" * StringUtils.substringBeforeLast("a", "z") = "a" * StringUtils.substringBeforeLast("a", null) = "a" * StringUtils.substringBeforeLast("a", "") = "a" * </pre> * * @param str the String to get a substring from, may be null * @param separator the String to search for, may be null * @return the substring before the last occurrence of the separator, * {@code null} if null String input * @since 2.0 */ public static String substringBeforeLast(final String str, final String separator) { if (isEmpty(str) || isEmpty(separator)) { return str; } final int pos = str.lastIndexOf(separator); if (pos == INDEX_NOT_FOUND) { return str; } return str.substring(0, pos); } /** * <p>Gets the substring before the first occurrence of a separator. * The separator is not returned.</p> * * <p>A {@code null} string input will return {@code null}. * An empty ("") string input will return the empty string. * A {@code null} separator will return the input string.</p> * * <p>If nothing is found, the string input is returned.</p> * * <pre> * StringUtils.substringBefore(null, *) = null * StringUtils.substringBefore("", *) = "" * StringUtils.substringBefore("abc", "a") = "" * StringUtils.substringBefore("abcba", "b") = "a" * StringUtils.substringBefore("abc", "c") = "ab" * StringUtils.substringBefore("abc", "d") = "abc" * StringUtils.substringBefore("abc", "") = "" * StringUtils.substringBefore("abc", null) = "abc" * </pre> * * @param str the String to get a substring from, may be null * @param separator the String to search for, may be null * @return the substring before the first occurrence of the separator, * {@code null} if null String input * @since 2.0 */ public static String substringBefore(final String str, final String separator) { if (isEmpty(str) || separator == null) { return str; } if (separator.isEmpty()) { return EMPTY; } final int pos = str.indexOf(separator); if (pos == INDEX_NOT_FOUND) { return str; } return str.substring(0, pos); } /** * Finds first occurrence of a substring in the given source but within limited range [start, end). * It is fastest possible code, but still original <code>String.indexOf(String, int)</code> * is much faster (since it uses char[] value directly) and should be used when no range is needed. * * @param src source string for examination * @param sub substring to find * @param startIndex starting index * @param endIndex ending index * @return index of founded substring or -1 if substring not found */ public static int indexOf(String src, String sub, int startIndex, int endIndex) { if (startIndex < 0) { startIndex = 0; } int srclen = src.length(); if (endIndex > srclen) { endIndex = srclen; } int sublen = sub.length(); if (sublen == 0) { return startIndex > srclen ? srclen : startIndex; } int total = endIndex - sublen + 1; char c = sub.charAt(0); mainloop: for (int i = startIndex; i < total; i++) { if (src.charAt(i) != c) { continue; } int j = 1; int k = i + 1; while (j < sublen) { if (sub.charAt(j) != src.charAt(k)) { continue mainloop; } j++; k++; } return i; } return -1; } /** * Finds the first occurrence of a character in the given source but within limited range (start, end]. */ public static int indexOf(String src, char c, int startIndex, int endIndex) { if (startIndex < 0) { startIndex = 0; } int srclen = src.length(); if (endIndex > srclen) { endIndex = srclen; } for (int i = startIndex; i < endIndex; i++) { if (src.charAt(i) == c) { return i; } } return -1; } public static String substringAfterLast(final String str, final String separator) { if (isEmpty(str)) { return str; } if (isEmpty(separator)) { return EMPTY; } final int pos = str.lastIndexOf(separator); if (pos == INDEX_NOT_FOUND || pos == str.length() - separator.length()) { return EMPTY; } return str.substring(pos + separator.length()); } /** * <p>Checks if the CharSequence contains only Unicode digits. * A decimal point is not a Unicode digit and returns false.</p> * * <p>{@code null} will return {@code false}. * An empty CharSequence (length()=0) will return {@code false}.</p> * * <p>Note that the method does not allow for a leading sign, either positive or negative. * Also, if a String passes the numeric test, it may still generate a NumberFormatException * when parsed by Integer.parseInt or Long.parseLong, e.g. if the value is outside the range * for int or long respectively.</p> * * <pre> * StringUtils.isNumeric(null) = false * StringUtils.isNumeric("") = false * StringUtils.isNumeric(" ") = false * StringUtils.isNumeric("123") = true * StringUtils.isNumeric("\u0967\u0968\u0969") = true * StringUtils.isNumeric("12 3") = false * StringUtils.isNumeric("ab2c") = false * StringUtils.isNumeric("12-3") = false * StringUtils.isNumeric("12.3") = false * StringUtils.isNumeric("-123") = false * StringUtils.isNumeric("+123") = false * </pre> * * @param cs the CharSequence to check, may be null * @return {@code true} if only contains digits, and is non-null * @since 3.0 Changed signature from isNumeric(String) to isNumeric(CharSequence) * @since 3.0 Changed "" to return false and not true */ public static boolean isNumeric(final CharSequence cs) { if (isEmpty(cs)) { return false; } final int sz = cs.length(); for (int i = 0; i < sz; i++) { if (Character.isDigit(cs.charAt(i)) == false) { return false; } } return true; } /** * <p>Checks if the CharSequence contains only lowercase characters.</p> * * <p>{@code null} will return {@code false}. * An empty CharSequence (length()=0) will return {@code false}.</p> * * <pre> * StringUtils.isAllLowerCase(null) = false * StringUtils.isAllLowerCase("") = false * StringUtils.isAllLowerCase(" ") = false * StringUtils.isAllLowerCase("abc") = true * StringUtils.isAllLowerCase("abC") = false * StringUtils.isAllLowerCase("ab c") = false * StringUtils.isAllLowerCase("ab1c") = false * StringUtils.isAllLowerCase("ab/c") = false * </pre> * * @param cs the CharSequence to check, may be null * @return {@code true} if only contains lowercase characters, and is non-null * @since 2.5 * @since 3.0 Changed signature from isAllLowerCase(String) to isAllLowerCase(CharSequence) */ public static boolean isAllLowerCase(final CharSequence cs) { if (cs == null || isEmpty(cs)) { return false; } final int sz = cs.length(); for (int i = 0; i < sz; i++) { if (Character.isLowerCase(cs.charAt(i)) == false) { return false; } } return true; } /** * <p>Checks if the CharSequence contains only uppercase characters.</p> * * <p>{@code null} will return {@code false}. * An empty String (length()=0) will return {@code false}.</p> * * <pre> * StringUtils.isAllUpperCase(null) = false * StringUtils.isAllUpperCase("") = false * StringUtils.isAllUpperCase(" ") = false * StringUtils.isAllUpperCase("ABC") = true * StringUtils.isAllUpperCase("aBC") = false * StringUtils.isAllUpperCase("A C") = false * StringUtils.isAllUpperCase("A1C") = false * StringUtils.isAllUpperCase("A/C") = false * </pre> * * @param cs the CharSequence to check, may be null * @return {@code true} if only contains uppercase characters, and is non-null * @since 2.5 * @since 3.0 Changed signature from isAllUpperCase(String) to isAllUpperCase(CharSequence) */ public static boolean isAllUpperCase(final CharSequence cs) { if (cs == null || isEmpty(cs)) { return false; } final int sz = cs.length(); for (int i = 0; i < sz; i++) { if (Character.isUpperCase(cs.charAt(i)) == false) { return false; } } return true; } /** * <p>Joins the elements of the provided {@code Iterator} into * a single String containing the provided elements.</p> * * <p>No delimiter is added before or after the list. * A {@code null} separator is the same as an empty String ("").</p> * * <p>See the examples here: {@link #join(Object[],String)}. </p> * * @param iterator the {@code Iterator} of values to join together, may be null * @param separator the separator character to use, null treated as "" * @return the joined String, {@code null} if null iterator input */ public static String join(final Iterator<?> iterator, final String separator) { // handle null, zero and one elements before building a buffer if (iterator == null) { return null; } if (!iterator.hasNext()) { return EMPTY; } final Object first = iterator.next(); if (!iterator.hasNext()) { return first == null ? "" : first.toString(); } // two or more elements final StringBuilder buf = new StringBuilder(256); // Java default is 16, probably too small if (first != null) { buf.append(first); } while (iterator.hasNext()) { if (separator != null) { buf.append(separator); } final Object obj = iterator.next(); if (obj != null) { buf.append(obj); } } return buf.toString(); } /** * <p>Joins the elements of the provided {@code Iterator} into * a single String containing the provided elements.</p> * * <p>No delimiter is added before or after the list. Null objects or empty * strings within the iteration are represented by empty strings.</p> * * <p>See the examples here: {@link #join(Object[],char)}. </p> * * @param iterator the {@code Iterator} of values to join together, may be null * @param separator the separator character to use * @return the joined String, {@code null} if null iterator input * @since 2.0 */ public static String join(final Iterator<?> iterator, final char separator) { // handle null, zero and one elements before building a buffer if (iterator == null) { return null; } if (!iterator.hasNext()) { return EMPTY; } final Object first = iterator.next(); if (!iterator.hasNext()) { return first == null ? "" : first.toString(); } // two or more elements final StringBuilder buf = new StringBuilder(256); // Java default is 16, probably too small if (first != null) { buf.append(first); } while (iterator.hasNext()) { buf.append(separator); final Object obj = iterator.next(); if (obj != null) { buf.append(obj); } } return buf.toString(); } /** * * @param kvs * @param separator * @return */ public static String join(List<String> kvs, char separator) { if (kvs == null) { return null; } return join(kvs.iterator(), separator); } }