| /* |
| * 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 org.apache.dubbo.common.utils; |
| |
| import org.apache.dubbo.common.io.UnsafeStringWriter; |
| import org.apache.dubbo.common.logger.Logger; |
| import org.apache.dubbo.common.logger.LoggerFactory; |
| |
| import java.io.PrintWriter; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeMap; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import static java.lang.String.valueOf; |
| import static java.util.Collections.emptySet; |
| import static java.util.Collections.unmodifiableSet; |
| import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SEPARATOR; |
| import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SPLIT_PATTERN; |
| import static org.apache.dubbo.common.constants.CommonConstants.DOT_REGEX; |
| import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY; |
| import static org.apache.dubbo.common.constants.CommonConstants.HIDE_KEY_PREFIX; |
| import static org.apache.dubbo.common.constants.CommonConstants.INTERFACE_KEY; |
| import static org.apache.dubbo.common.constants.CommonConstants.SEPARATOR_REGEX; |
| import static org.apache.dubbo.common.constants.CommonConstants.UNDERLINE_SEPARATOR; |
| import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY; |
| |
| /** |
| * StringUtils |
| */ |
| |
| public final class StringUtils { |
| |
| public static final String EMPTY_STRING = ""; |
| public static final int INDEX_NOT_FOUND = -1; |
| public static final String[] EMPTY_STRING_ARRAY = new String[0]; |
| |
| private static final Logger logger = LoggerFactory.getLogger(StringUtils.class); |
| private static final Pattern KVP_PATTERN = Pattern.compile("([_.a-zA-Z0-9][-_.a-zA-Z0-9]*)[=](.*)"); //key value pair pattern. |
| private static final Pattern NUM_PATTERN = Pattern.compile("^\\d+$"); |
| private static final Pattern PARAMETERS_PATTERN = Pattern.compile("^\\[((\\s*\\{\\s*[\\w_\\-\\.]+\\s*:\\s*.+?\\s*\\}\\s*,?\\s*)+)\\s*\\]$"); |
| private static final Pattern PAIR_PARAMETERS_PATTERN = Pattern.compile("^\\{\\s*([\\w-_\\.]+)\\s*:\\s*(.+)\\s*\\}$"); |
| private static final int PAD_LIMIT = 8192; |
| private static final byte[] HEX2B; |
| |
| |
| /** |
| * @since 2.7.5 |
| */ |
| public static final char EQUAL_CHAR = '='; |
| |
| public static final String EQUAL = valueOf(EQUAL_CHAR); |
| |
| public static final char AND_CHAR = '&'; |
| |
| public static final String AND = valueOf(AND_CHAR); |
| |
| public static final char SEMICOLON_CHAR = ';'; |
| |
| public static final String SEMICOLON = valueOf(SEMICOLON_CHAR); |
| |
| public static final char QUESTION_MASK_CHAR = '?'; |
| |
| public static final String QUESTION_MASK = valueOf(QUESTION_MASK_CHAR); |
| |
| public static final char SLASH_CHAR = '/'; |
| |
| public static final String SLASH = valueOf(SLASH_CHAR); |
| |
| public static final char HYPHEN_CHAR = '-'; |
| |
| public static final String HYPHEN = valueOf(HYPHEN_CHAR); |
| |
| static { |
| HEX2B = new byte[128]; |
| Arrays.fill(HEX2B, (byte) -1); |
| HEX2B['0'] = (byte) 0; |
| HEX2B['1'] = (byte) 1; |
| HEX2B['2'] = (byte) 2; |
| HEX2B['3'] = (byte) 3; |
| HEX2B['4'] = (byte) 4; |
| HEX2B['5'] = (byte) 5; |
| HEX2B['6'] = (byte) 6; |
| HEX2B['7'] = (byte) 7; |
| HEX2B['8'] = (byte) 8; |
| HEX2B['9'] = (byte) 9; |
| HEX2B['A'] = (byte) 10; |
| HEX2B['B'] = (byte) 11; |
| HEX2B['C'] = (byte) 12; |
| HEX2B['D'] = (byte) 13; |
| HEX2B['E'] = (byte) 14; |
| HEX2B['F'] = (byte) 15; |
| HEX2B['a'] = (byte) 10; |
| HEX2B['b'] = (byte) 11; |
| HEX2B['c'] = (byte) 12; |
| HEX2B['d'] = (byte) 13; |
| HEX2B['e'] = (byte) 14; |
| HEX2B['f'] = (byte) 15; |
| } |
| |
| private StringUtils() { |
| } |
| |
| /** |
| * Gets a CharSequence length or {@code 0} if the CharSequence is |
| * {@code null}. |
| * |
| * @param cs a CharSequence or {@code null} |
| * @return CharSequence length or {@code 0} if the CharSequence is |
| * {@code null}. |
| */ |
| public static int length(final CharSequence cs) { |
| return cs == null ? 0 : cs.length(); |
| } |
| |
| /** |
| * <p>Repeat a String {@code repeat} times to form a |
| * new String.</p> |
| * |
| * <pre> |
| * StringUtils.repeat(null, 2) = null |
| * StringUtils.repeat("", 0) = "" |
| * StringUtils.repeat("", 2) = "" |
| * StringUtils.repeat("a", 3) = "aaa" |
| * StringUtils.repeat("ab", 2) = "abab" |
| * StringUtils.repeat("a", -2) = "" |
| * </pre> |
| * |
| * @param str the String to repeat, may be null |
| * @param repeat number of times to repeat str, negative treated as zero |
| * @return a new String consisting of the original String repeated, |
| * {@code null} if null String input |
| */ |
| public static String repeat(final String str, final int repeat) { |
| // Performance tuned for 2.0 (JDK1.4) |
| |
| if (str == null) { |
| return null; |
| } |
| if (repeat <= 0) { |
| return EMPTY_STRING; |
| } |
| final int inputLength = str.length(); |
| if (repeat == 1 || inputLength == 0) { |
| return str; |
| } |
| if (inputLength == 1 && repeat <= PAD_LIMIT) { |
| return repeat(str.charAt(0), repeat); |
| } |
| |
| final int outputLength = inputLength * repeat; |
| switch (inputLength) { |
| case 1: |
| return repeat(str.charAt(0), repeat); |
| case 2: |
| final char ch0 = str.charAt(0); |
| final char ch1 = str.charAt(1); |
| final char[] output2 = new char[outputLength]; |
| for (int i = repeat * 2 - 2; i >= 0; i--, i--) { |
| output2[i] = ch0; |
| output2[i + 1] = ch1; |
| } |
| return new String(output2); |
| default: |
| final StringBuilder buf = new StringBuilder(outputLength); |
| for (int i = 0; i < repeat; i++) { |
| buf.append(str); |
| } |
| return buf.toString(); |
| } |
| } |
| |
| /** |
| * <p>Repeat a String {@code repeat} times to form a |
| * new String, with a String separator injected each time. </p> |
| * |
| * <pre> |
| * StringUtils.repeat(null, null, 2) = null |
| * StringUtils.repeat(null, "x", 2) = null |
| * StringUtils.repeat("", null, 0) = "" |
| * StringUtils.repeat("", "", 2) = "" |
| * StringUtils.repeat("", "x", 3) = "xxx" |
| * StringUtils.repeat("?", ", ", 3) = "?, ?, ?" |
| * </pre> |
| * |
| * @param str the String to repeat, may be null |
| * @param separator the String to inject, may be null |
| * @param repeat number of times to repeat str, negative treated as zero |
| * @return a new String consisting of the original String repeated, |
| * {@code null} if null String input |
| * @since 2.5 |
| */ |
| public static String repeat(final String str, final String separator, final int repeat) { |
| if (str == null || separator == null) { |
| return repeat(str, repeat); |
| } |
| // given that repeat(String, int) is quite optimized, better to rely on it than try and splice this into it |
| final String result = repeat(str + separator, repeat); |
| return removeEnd(result, separator); |
| } |
| |
| /** |
| * <p>Removes a substring only if it is at the end of a source string, |
| * otherwise returns the source string.</p> |
| * |
| * <p>A {@code null} source string will return {@code null}. |
| * An empty ("") source string will return the empty string. |
| * A {@code null} search string will return the source string.</p> |
| * |
| * <pre> |
| * StringUtils.removeEnd(null, *) = null |
| * StringUtils.removeEnd("", *) = "" |
| * StringUtils.removeEnd(*, null) = * |
| * StringUtils.removeEnd("www.domain.com", ".com.") = "www.domain.com" |
| * StringUtils.removeEnd("www.domain.com", ".com") = "www.domain" |
| * StringUtils.removeEnd("www.domain.com", "domain") = "www.domain.com" |
| * StringUtils.removeEnd("abc", "") = "abc" |
| * </pre> |
| * |
| * @param str the source String to search, may be null |
| * @param remove the String to search for and remove, may be null |
| * @return the substring with the string removed if found, |
| * {@code null} if null String input |
| */ |
| public static String removeEnd(final String str, final String remove) { |
| if (isAnyEmpty(str, remove)) { |
| return str; |
| } |
| if (str.endsWith(remove)) { |
| return str.substring(0, str.length() - remove.length()); |
| } |
| return str; |
| } |
| |
| /** |
| * <p>Returns padding using the specified delimiter repeated |
| * to a given length.</p> |
| * |
| * <pre> |
| * StringUtils.repeat('e', 0) = "" |
| * StringUtils.repeat('e', 3) = "eee" |
| * StringUtils.repeat('e', -2) = "" |
| * </pre> |
| * |
| * <p>Note: this method doesn't not support padding with |
| * <a href="http://www.unicode.org/glossary/#supplementary_character">Unicode Supplementary Characters</a> |
| * as they require a pair of {@code char}s to be represented. |
| * If you are needing to support full I18N of your applications |
| * consider using {@link #repeat(String, int)} instead. |
| * </p> |
| * |
| * @param ch character to repeat |
| * @param repeat number of times to repeat char, negative treated as zero |
| * @return String with repeated character |
| * @see #repeat(String, int) |
| */ |
| public static String repeat(final char ch, final int repeat) { |
| final char[] buf = new char[repeat]; |
| for (int i = repeat - 1; i >= 0; i--) { |
| buf[i] = ch; |
| } |
| return new String(buf); |
| } |
| |
| /** |
| * <p>Strips any of a set of characters from the end of a String.</p> |
| * |
| * <p>A {@code null} input String returns {@code null}. |
| * An empty string ("") input returns the empty string.</p> |
| * |
| * <p>If the stripChars String is {@code null}, whitespace is |
| * stripped as defined by {@link Character#isWhitespace(char)}.</p> |
| * |
| * <pre> |
| * StringUtils.stripEnd(null, *) = null |
| * StringUtils.stripEnd("", *) = "" |
| * StringUtils.stripEnd("abc", "") = "abc" |
| * StringUtils.stripEnd("abc", null) = "abc" |
| * StringUtils.stripEnd(" abc", null) = " abc" |
| * StringUtils.stripEnd("abc ", null) = "abc" |
| * StringUtils.stripEnd(" abc ", null) = " abc" |
| * StringUtils.stripEnd(" abcyx", "xyz") = " abc" |
| * StringUtils.stripEnd("120.00", ".0") = "12" |
| * </pre> |
| * |
| * @param str the String to remove characters from, may be null |
| * @param stripChars the set of characters to remove, null treated as whitespace |
| * @return the stripped String, {@code null} if null String input |
| */ |
| public static String stripEnd(final String str, final String stripChars) { |
| int end; |
| if (str == null || (end = str.length()) == 0) { |
| return str; |
| } |
| |
| if (stripChars == null) { |
| while (end != 0 && Character.isWhitespace(str.charAt(end - 1))) { |
| end--; |
| } |
| } else if (stripChars.isEmpty()) { |
| return str; |
| } else { |
| while (end != 0 && stripChars.indexOf(str.charAt(end - 1)) != INDEX_NOT_FOUND) { |
| end--; |
| } |
| } |
| return str.substring(0, end); |
| } |
| |
| /** |
| * <p>Replaces all occurrences of a String within another String.</p> |
| * |
| * <p>A {@code null} reference passed to this method is a no-op.</p> |
| * |
| * <pre> |
| * StringUtils.replace(null, *, *) = null |
| * StringUtils.replace("", *, *) = "" |
| * StringUtils.replace("any", null, *) = "any" |
| * StringUtils.replace("any", *, null) = "any" |
| * StringUtils.replace("any", "", *) = "any" |
| * StringUtils.replace("aba", "a", null) = "aba" |
| * StringUtils.replace("aba", "a", "") = "b" |
| * StringUtils.replace("aba", "a", "z") = "zbz" |
| * </pre> |
| * |
| * @param text text to search and replace in, may be null |
| * @param searchString the String to search for, may be null |
| * @param replacement the String to replace it with, may be null |
| * @return the text with any replacements processed, |
| * {@code null} if null String input |
| * @see #replace(String text, String searchString, String replacement, int max) |
| */ |
| public static String replace(final String text, final String searchString, final String replacement) { |
| return replace(text, searchString, replacement, -1); |
| } |
| |
| /** |
| * <p>Replaces a String with another String inside a larger String, |
| * for the first {@code max} values of the search String.</p> |
| * |
| * <p>A {@code null} reference passed to this method is a no-op.</p> |
| * |
| * <pre> |
| * StringUtils.replace(null, *, *, *) = null |
| * StringUtils.replace("", *, *, *) = "" |
| * StringUtils.replace("any", null, *, *) = "any" |
| * StringUtils.replace("any", *, null, *) = "any" |
| * StringUtils.replace("any", "", *, *) = "any" |
| * StringUtils.replace("any", *, *, 0) = "any" |
| * StringUtils.replace("abaa", "a", null, -1) = "abaa" |
| * StringUtils.replace("abaa", "a", "", -1) = "b" |
| * StringUtils.replace("abaa", "a", "z", 0) = "abaa" |
| * StringUtils.replace("abaa", "a", "z", 1) = "zbaa" |
| * StringUtils.replace("abaa", "a", "z", 2) = "zbza" |
| * StringUtils.replace("abaa", "a", "z", -1) = "zbzz" |
| * </pre> |
| * |
| * @param text text to search and replace in, may be null |
| * @param searchString the String to search for, may be null |
| * @param replacement the String to replace it with, may be null |
| * @param max maximum number of values to replace, or {@code -1} if no maximum |
| * @return the text with any replacements processed, |
| * {@code null} if null String input |
| */ |
| public static String replace(final String text, final String searchString, final String replacement, int max) { |
| if (isAnyEmpty(text, searchString) || replacement == null || max == 0) { |
| return text; |
| } |
| int start = 0; |
| int end = text.indexOf(searchString, start); |
| if (end == INDEX_NOT_FOUND) { |
| return text; |
| } |
| final int replLength = searchString.length(); |
| int increase = replacement.length() - replLength; |
| increase = increase < 0 ? 0 : increase; |
| increase *= max < 0 ? 16 : max > 64 ? 64 : max; |
| final StringBuilder buf = new StringBuilder(text.length() + increase); |
| while (end != INDEX_NOT_FOUND) { |
| buf.append(text, start, end).append(replacement); |
| start = end + replLength; |
| if (--max == 0) { |
| break; |
| } |
| end = text.indexOf(searchString, start); |
| } |
| buf.append(text.substring(start)); |
| return buf.toString(); |
| } |
| |
| public static boolean isBlank(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))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * is not blank string. |
| * |
| * @param cs source string. |
| * @return is not blank. |
| */ |
| public static boolean isNotBlank(CharSequence cs) { |
| return !isBlank(cs); |
| } |
| |
| /** |
| * Check the cs String whether contains non whitespace characters. |
| * |
| * @param cs |
| * @return |
| */ |
| public static boolean hasText(CharSequence cs) { |
| return !isBlank(cs); |
| } |
| |
| /** |
| * is empty string. |
| * |
| * @param str source string. |
| * @return is empty. |
| */ |
| public static boolean isEmpty(String str) { |
| return str == null || str.isEmpty(); |
| } |
| |
| /** |
| * <p>Checks if the strings contain empty or null elements. <p/> |
| * |
| * <pre> |
| * StringUtils.isNoneEmpty(null) = false |
| * StringUtils.isNoneEmpty("") = false |
| * StringUtils.isNoneEmpty(" ") = true |
| * StringUtils.isNoneEmpty("abc") = true |
| * StringUtils.isNoneEmpty("abc", "def") = true |
| * StringUtils.isNoneEmpty("abc", null) = false |
| * StringUtils.isNoneEmpty("abc", "") = false |
| * StringUtils.isNoneEmpty("abc", " ") = true |
| * </pre> |
| * |
| * @param ss the strings to check |
| * @return {@code true} if all strings are not empty or null |
| */ |
| public static boolean isNoneEmpty(final String... ss) { |
| if (ArrayUtils.isEmpty(ss)) { |
| return false; |
| } |
| for (final String s : ss) { |
| if (isEmpty(s)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * <p>Checks if the strings contain at least on empty or null element. <p/> |
| * |
| * <pre> |
| * StringUtils.isAnyEmpty(null) = true |
| * StringUtils.isAnyEmpty("") = true |
| * StringUtils.isAnyEmpty(" ") = false |
| * StringUtils.isAnyEmpty("abc") = false |
| * StringUtils.isAnyEmpty("abc", "def") = false |
| * StringUtils.isAnyEmpty("abc", null) = true |
| * StringUtils.isAnyEmpty("abc", "") = true |
| * StringUtils.isAnyEmpty("abc", " ") = false |
| * </pre> |
| * |
| * @param ss the strings to check |
| * @return {@code true} if at least one in the strings is empty or null |
| */ |
| public static boolean isAnyEmpty(final String... ss) { |
| return !isNoneEmpty(ss); |
| } |
| |
| /** |
| * is not empty string. |
| * |
| * @param str source string. |
| * @return is not empty. |
| */ |
| public static boolean isNotEmpty(String str) { |
| return !isEmpty(str); |
| } |
| |
| /** |
| * if s1 is null and s2 is null, then return true |
| * |
| * @param s1 str1 |
| * @param s2 str2 |
| * @return equals |
| */ |
| public static boolean isEquals(String s1, String s2) { |
| if (s1 == null && s2 == null) { |
| return true; |
| } |
| if (s1 == null || s2 == null) { |
| return false; |
| } |
| return s1.equals(s2); |
| } |
| |
| /** |
| * is positive integer or zero string. |
| * |
| * @param str a string |
| * @return is positive integer or zero |
| */ |
| public static boolean isNumber(String str) { |
| return isNotEmpty(str) && NUM_PATTERN.matcher(str).matches(); |
| } |
| |
| /** |
| * parse str to Integer(if str is not number or n < 0, then return 0) |
| * |
| * @param str a number str |
| * @return positive integer or zero |
| */ |
| public static int parseInteger(String str) { |
| return isNumber(str) ? Integer.parseInt(str) : 0; |
| } |
| |
| /** |
| * parse str to Long(if str is not number or n < 0, then return 0) |
| * |
| * @param str a number str |
| * @return positive long or zero |
| */ |
| public static long parseLong(String str) { |
| return isNumber(str) ? Long.parseLong(str) : 0; |
| } |
| |
| /** |
| * Returns true if s is a legal Java identifier.<p> |
| * <a href="http://www.exampledepot.com/egs/java.lang/IsJavaId.html">more info.</a> |
| */ |
| public static boolean isJavaIdentifier(String s) { |
| if (isEmpty(s) || !Character.isJavaIdentifierStart(s.charAt(0))) { |
| return false; |
| } |
| for (int i = 1; i < s.length(); i++) { |
| if (!Character.isJavaIdentifierPart(s.charAt(i))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| public static boolean isContains(String values, String value) { |
| return isNotEmpty(values) && isContains(COMMA_SPLIT_PATTERN.split(values), value); |
| } |
| |
| public static boolean isContains(String str, char ch) { |
| return isNotEmpty(str) && str.indexOf(ch) >= 0; |
| } |
| |
| public static boolean isNotContains(String str, char ch) { |
| return !isContains(str, ch); |
| } |
| |
| /** |
| * @param values |
| * @param value |
| * @return contains |
| */ |
| public static boolean isContains(String[] values, String value) { |
| if (isNotEmpty(value) && ArrayUtils.isNotEmpty(values)) { |
| for (String v : values) { |
| if (value.equals(v)) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| public static boolean isNumeric(String str, boolean allowDot) { |
| if (str == null || str.isEmpty()) { |
| return false; |
| } |
| boolean hasDot = false; |
| int sz = str.length(); |
| for (int i = 0; i < sz; i++) { |
| if (str.charAt(i) == '.') { |
| if (hasDot || !allowDot) { |
| return false; |
| } |
| hasDot = true; |
| continue; |
| } |
| if (!Character.isDigit(str.charAt(i))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| |
| /** |
| * @param e |
| * @return string |
| */ |
| public static String toString(Throwable e) { |
| UnsafeStringWriter w = new UnsafeStringWriter(); |
| PrintWriter p = new PrintWriter(w); |
| p.print(e.getClass().getName()); |
| if (e.getMessage() != null) { |
| p.print(": " + e.getMessage()); |
| } |
| p.println(); |
| try { |
| e.printStackTrace(p); |
| return w.toString(); |
| } finally { |
| p.close(); |
| } |
| } |
| |
| /** |
| * @param msg |
| * @param e |
| * @return string |
| */ |
| public static String toString(String msg, Throwable e) { |
| UnsafeStringWriter w = new UnsafeStringWriter(); |
| w.write(msg + "\n"); |
| PrintWriter p = new PrintWriter(w); |
| try { |
| e.printStackTrace(p); |
| return w.toString(); |
| } finally { |
| p.close(); |
| } |
| } |
| |
| /** |
| * translate. |
| * |
| * @param src source string. |
| * @param from src char table. |
| * @param to target char table. |
| * @return String. |
| */ |
| public static String translate(String src, String from, String to) { |
| if (isEmpty(src)) { |
| return src; |
| } |
| StringBuilder sb = null; |
| int ix; |
| char c; |
| for (int i = 0, len = src.length(); i < len; i++) { |
| c = src.charAt(i); |
| ix = from.indexOf(c); |
| if (ix == -1) { |
| if (sb != null) { |
| sb.append(c); |
| } |
| } else { |
| if (sb == null) { |
| sb = new StringBuilder(len); |
| sb.append(src, 0, i); |
| } |
| if (ix < to.length()) { |
| sb.append(to.charAt(ix)); |
| } |
| } |
| } |
| return sb == null ? src : sb.toString(); |
| } |
| |
| /** |
| * split. |
| * |
| * @param ch char. |
| * @return string array. |
| */ |
| public static String[] split(String str, char ch) { |
| if (isEmpty(str)) { |
| return EMPTY_STRING_ARRAY; |
| } |
| return splitToList0(str, ch).toArray(EMPTY_STRING_ARRAY); |
| } |
| |
| private static List<String> splitToList0(String str, char ch) { |
| List<String> result = new ArrayList<>(); |
| int ix = 0, len = str.length(); |
| for (int i = 0; i < len; i++) { |
| if (str.charAt(i) == ch) { |
| result.add(str.substring(ix, i)); |
| ix = i + 1; |
| } |
| } |
| |
| if (ix >= 0) { |
| result.add(str.substring(ix)); |
| } |
| return result; |
| } |
| |
| /** |
| * Splits String around matches of the given character. |
| * <p> |
| * Note: Compare with {@link StringUtils#split(String, char)}, this method reduce memory copy. |
| */ |
| public static List<String> splitToList(String str, char ch) { |
| if (isEmpty(str)) { |
| return Collections.emptyList(); |
| } |
| return splitToList0(str, ch); |
| } |
| |
| /** |
| * Split the specified value to be a {@link Set} |
| * |
| * @param value the content to be split |
| * @param separatorChar a char to separate |
| * @return non-null read-only {@link Set} |
| * @since 2.7.8 |
| */ |
| public static Set<String> splitToSet(String value, char separatorChar) { |
| return splitToSet(value, separatorChar, false); |
| } |
| |
| /** |
| * Split the specified value to be a {@link Set} |
| * |
| * @param value the content to be split |
| * @param separatorChar a char to separate |
| * @param trimElements require to trim the elements or not |
| * @return non-null read-only {@link Set} |
| * @since 2.7.8 |
| */ |
| public static Set<String> splitToSet(String value, char separatorChar, boolean trimElements) { |
| List<String> values = splitToList(value, separatorChar); |
| int size = values.size(); |
| |
| if (size < 1) { // empty condition |
| return emptySet(); |
| } |
| |
| if (!trimElements) { // Do not require to trim the elements |
| return new LinkedHashSet(values); |
| } |
| |
| return unmodifiableSet(values |
| .stream() |
| .map(String::trim) |
| .collect(LinkedHashSet::new, Set::add, Set::addAll)); |
| } |
| |
| /** |
| * join string. |
| * |
| * @param array String array. |
| * @return String. |
| */ |
| public static String join(String[] array) { |
| if (ArrayUtils.isEmpty(array)) { |
| return EMPTY_STRING; |
| } |
| StringBuilder sb = new StringBuilder(); |
| for (String s : array) { |
| sb.append(s); |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * join string like javascript. |
| * |
| * @param array String array. |
| * @param split split |
| * @return String. |
| */ |
| public static String join(String[] array, char split) { |
| if (ArrayUtils.isEmpty(array)) { |
| return EMPTY_STRING; |
| } |
| StringBuilder sb = new StringBuilder(); |
| for (int i = 0; i < array.length; i++) { |
| if (i > 0) { |
| sb.append(split); |
| } |
| sb.append(array[i]); |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * join string like javascript. |
| * |
| * @param array String array. |
| * @param split split |
| * @return String. |
| */ |
| public static String join(String[] array, String split) { |
| if (ArrayUtils.isEmpty(array)) { |
| return EMPTY_STRING; |
| } |
| StringBuilder sb = new StringBuilder(); |
| for (int i = 0; i < array.length; i++) { |
| if (i > 0) { |
| sb.append(split); |
| } |
| sb.append(array[i]); |
| } |
| return sb.toString(); |
| } |
| |
| public static String join(Collection<String> coll, String split) { |
| if (CollectionUtils.isEmpty(coll)) { |
| return EMPTY_STRING; |
| } |
| |
| StringBuilder sb = new StringBuilder(); |
| boolean isFirst = true; |
| for (String s : coll) { |
| if (isFirst) { |
| isFirst = false; |
| } else { |
| sb.append(split); |
| } |
| sb.append(s); |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * parse key-value pair. |
| * |
| * @param str string. |
| * @param itemSeparator item separator. |
| * @return key-value map; |
| */ |
| private static Map<String, String> parseKeyValuePair(String str, String itemSeparator) { |
| String[] tmp = str.split(itemSeparator); |
| Map<String, String> map = new HashMap<String, String>(tmp.length); |
| for (int i = 0; i < tmp.length; i++) { |
| Matcher matcher = KVP_PATTERN.matcher(tmp[i]); |
| if (!matcher.matches()) { |
| continue; |
| } |
| map.put(matcher.group(1), matcher.group(2)); |
| } |
| return map; |
| } |
| |
| public static String getQueryStringValue(String qs, String key) { |
| Map<String, String> map = parseQueryString(qs); |
| return map.get(key); |
| } |
| |
| /** |
| * parse query string to Parameters. |
| * |
| * @param qs query string. |
| * @return Parameters instance. |
| */ |
| public static Map<String, String> parseQueryString(String qs) { |
| if (isEmpty(qs)) { |
| return new HashMap<String, String>(); |
| } |
| return parseKeyValuePair(qs, "\\&"); |
| } |
| |
| public static String getServiceKey(Map<String, String> ps) { |
| StringBuilder buf = new StringBuilder(); |
| String group = ps.get(GROUP_KEY); |
| if (isNotEmpty(group)) { |
| buf.append(group).append('/'); |
| } |
| buf.append(ps.get(INTERFACE_KEY)); |
| String version = ps.get(VERSION_KEY); |
| if (isNotEmpty(group)) { |
| buf.append(':').append(version); |
| } |
| return buf.toString(); |
| } |
| |
| public static String toQueryString(Map<String, String> ps) { |
| StringBuilder buf = new StringBuilder(); |
| if (ps != null && ps.size() > 0) { |
| for (Map.Entry<String, String> entry : new TreeMap<String, String>(ps).entrySet()) { |
| String key = entry.getKey(); |
| String value = entry.getValue(); |
| if (isNoneEmpty(key, value)) { |
| if (buf.length() > 0) { |
| buf.append('&'); |
| } |
| buf.append(key); |
| buf.append('='); |
| buf.append(value); |
| } |
| } |
| } |
| return buf.toString(); |
| } |
| |
| public static String camelToSplitName(String camelName, String split) { |
| if (isEmpty(camelName)) { |
| return camelName; |
| } |
| if (!isWord(camelName)) { |
| // convert Ab-Cd-Ef to ab-cd-ef |
| if (isSplitCase(camelName, split.charAt(0))) { |
| return camelName.toLowerCase(); |
| } |
| // not camel case |
| return camelName; |
| } |
| |
| StringBuilder buf = null; |
| for (int i = 0; i < camelName.length(); i++) { |
| char ch = camelName.charAt(i); |
| if (ch >= 'A' && ch <= 'Z') { |
| if (buf == null) { |
| buf = new StringBuilder(); |
| if (i > 0) { |
| buf.append(camelName, 0, i); |
| } |
| } |
| if (i > 0) { |
| buf.append(split); |
| } |
| buf.append(Character.toLowerCase(ch)); |
| } else if (buf != null) { |
| buf.append(ch); |
| } |
| } |
| return buf == null ? camelName.toLowerCase() : buf.toString().toLowerCase(); |
| } |
| |
| private static boolean isSplitCase(String str, char separator) { |
| if (str == null) { |
| return false; |
| } |
| return str.chars().allMatch(ch -> (ch == separator) || isWord((char) ch)); |
| } |
| |
| private static boolean isWord(String str) { |
| if (str == null) { |
| return false; |
| } |
| return str.chars().allMatch(ch -> isWord((char) ch)); |
| } |
| |
| private static boolean isWord(char ch) { |
| if ((ch >= 'A' && ch <= 'Z') || |
| (ch >= 'a' && ch <= 'z') || |
| (ch >= '0' && ch <= '9')) { |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Convert snake_case or SNAKE_CASE to kebab-case. |
| * <p> |
| * NOTE: Return itself if it's not a snake case. |
| * |
| * @param snakeName |
| * @param split |
| * @return |
| */ |
| public static String snakeToSplitName(String snakeName, String split) { |
| String lowerCase = snakeName.toLowerCase(); |
| if (isSnakeCase(snakeName)) { |
| return replace(lowerCase, "_", split); |
| } |
| return snakeName; |
| } |
| |
| protected static boolean isSnakeCase(String str) { |
| return str.contains("_") || str.equals(str.toLowerCase()) || str.equals(str.toUpperCase()); |
| } |
| |
| /** |
| * Convert camelCase or snake_case/SNAKE_CASE to kebab-case |
| * |
| * @param str |
| * @param split |
| * @return |
| */ |
| public static String convertToSplitName(String str, String split) { |
| if (isSnakeCase(str)) { |
| return snakeToSplitName(str, split); |
| } else { |
| return camelToSplitName(str, split); |
| } |
| } |
| |
| public static String toArgumentString(Object[] args) { |
| StringBuilder buf = new StringBuilder(); |
| for (Object arg : args) { |
| if (buf.length() > 0) { |
| buf.append(COMMA_SEPARATOR); |
| } |
| if (arg == null || ReflectUtils.isPrimitives(arg.getClass())) { |
| buf.append(arg); |
| } else { |
| try { |
| buf.append(JsonUtils.getJson().toJson(arg)); |
| } catch (Exception e) { |
| logger.warn(e.getMessage(), e); |
| buf.append(arg); |
| } |
| } |
| } |
| return buf.toString(); |
| } |
| |
| public static String trim(String str) { |
| return str == null ? null : str.trim(); |
| } |
| |
| public static String toURLKey(String key) { |
| return key.toLowerCase().replaceAll(SEPARATOR_REGEX, HIDE_KEY_PREFIX); |
| } |
| |
| public static String toOSStyleKey(String key) { |
| key = key.toUpperCase().replaceAll(DOT_REGEX, UNDERLINE_SEPARATOR); |
| if (!key.startsWith("DUBBO_")) { |
| key = "DUBBO_" + key; |
| } |
| return key; |
| } |
| |
| public static boolean isAllUpperCase(String str) { |
| if (str != null && !isEmpty(str)) { |
| int sz = str.length(); |
| |
| for (int i = 0; i < sz; ++i) { |
| if (!Character.isUpperCase(str.charAt(i))) { |
| return false; |
| } |
| } |
| |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| public static String[] delimitedListToStringArray(String str, String delimiter) { |
| return delimitedListToStringArray(str, delimiter, (String) null); |
| } |
| |
| public static String[] delimitedListToStringArray(String str, String delimiter, String charsToDelete) { |
| if (str == null) { |
| return new String[0]; |
| } else if (delimiter == null) { |
| return new String[]{str}; |
| } else { |
| List<String> result = new ArrayList(); |
| int pos; |
| if ("".equals(delimiter)) { |
| for (pos = 0; pos < str.length(); ++pos) { |
| result.add(deleteAny(str.substring(pos, pos + 1), charsToDelete)); |
| } |
| } else { |
| int delPos; |
| for (pos = 0; (delPos = str.indexOf(delimiter, pos)) != -1; pos = delPos + delimiter.length()) { |
| result.add(deleteAny(str.substring(pos, delPos), charsToDelete)); |
| } |
| |
| if (str.length() > 0 && pos <= str.length()) { |
| result.add(deleteAny(str.substring(pos), charsToDelete)); |
| } |
| } |
| |
| return toStringArray((Collection) result); |
| } |
| } |
| |
| public static String arrayToDelimitedString(Object[] arr, String delim) { |
| if (ArrayUtils.isEmpty(arr)) { |
| return ""; |
| } else if (arr.length == 1) { |
| return nullSafeToString(arr[0]); |
| } else { |
| StringBuilder sb = new StringBuilder(); |
| |
| for (int i = 0; i < arr.length; ++i) { |
| if (i > 0) { |
| sb.append(delim); |
| } |
| |
| sb.append(arr[i]); |
| } |
| |
| return sb.toString(); |
| } |
| } |
| |
| public static String deleteAny(String inString, String charsToDelete) { |
| if (isNotEmpty(inString) && isNotEmpty(charsToDelete)) { |
| StringBuilder sb = new StringBuilder(inString.length()); |
| |
| for (int i = 0; i < inString.length(); ++i) { |
| char c = inString.charAt(i); |
| if (charsToDelete.indexOf(c) == -1) { |
| sb.append(c); |
| } |
| } |
| |
| return sb.toString(); |
| } else { |
| return inString; |
| } |
| } |
| |
| public static String[] toStringArray(Collection<String> collection) { |
| return (String[]) collection.toArray(new String[0]); |
| } |
| |
| public static String nullSafeToString(Object obj) { |
| if (obj == null) { |
| return "null"; |
| } else if (obj instanceof String) { |
| return (String) obj; |
| } else { |
| String str = obj.toString(); |
| return str != null ? str : ""; |
| } |
| } |
| |
| /** |
| * Decode parameters string to map |
| * |
| * @param rawParameters format like '[{a:b},{c:d}]' |
| * @return |
| */ |
| public static Map<String, String> parseParameters(String rawParameters) { |
| if (StringUtils.isBlank(rawParameters)) { |
| return Collections.emptyMap(); |
| } |
| Matcher matcher = PARAMETERS_PATTERN.matcher(rawParameters); |
| if (!matcher.matches()) { |
| return Collections.emptyMap(); |
| } |
| |
| String pairs = matcher.group(1); |
| String[] pairArr = pairs.split("\\s*,\\s*"); |
| |
| Map<String, String> parameters = new HashMap<>(); |
| for (String pair : pairArr) { |
| Matcher pairMatcher = PAIR_PARAMETERS_PATTERN.matcher(pair); |
| if (pairMatcher.matches()) { |
| parameters.put(pairMatcher.group(1), pairMatcher.group(2)); |
| } |
| } |
| return parameters; |
| } |
| |
| /** |
| * Encode parameters map to string, like '[{a:b},{c:d}]' |
| * |
| * @param params |
| * @return |
| */ |
| public static String encodeParameters(Map<String, String> params) { |
| if (params == null || params.isEmpty()) { |
| return null; |
| } |
| |
| StringBuilder sb = new StringBuilder(); |
| sb.append('['); |
| params.forEach((key, value) -> { |
| // {key:value}, |
| if (hasText(value)) { |
| sb.append('{').append(key).append(':').append(value).append("},"); |
| } |
| }); |
| // delete last separator ',' |
| if (sb.charAt(sb.length() - 1) == ',') { |
| sb.deleteCharAt(sb.length() - 1); |
| } |
| sb.append(']'); |
| return sb.toString(); |
| } |
| |
| public static int decodeHexNibble(final char c) { |
| // Character.digit() is not used here, as it addresses a larger |
| // set of characters (both ASCII and full-width latin letters). |
| byte[] hex2b = HEX2B; |
| return c < hex2b.length ? hex2b[c] : -1; |
| } |
| |
| /** |
| * Decode a 2-digit hex byte from within a string. |
| */ |
| public static byte decodeHexByte(CharSequence s, int pos) { |
| int hi = decodeHexNibble(s.charAt(pos)); |
| int lo = decodeHexNibble(s.charAt(pos + 1)); |
| if (hi == -1 || lo == -1) { |
| throw new IllegalArgumentException(String.format( |
| "invalid hex byte '%s' at index %d of '%s'", s.subSequence(pos, pos + 2), pos, s)); |
| } |
| return (byte) ((hi << 4) + lo); |
| } |
| |
| /** |
| * Create the common-delimited {@link String} by one or more {@link String} members |
| * |
| * @param one one {@link String} |
| * @param others others {@link String} |
| * @return <code>null</code> if <code>one</code> or <code>others</code> is <code>null</code> |
| * @since 2.7.8 |
| */ |
| public static String toCommaDelimitedString(String one, String... others) { |
| String another = arrayToDelimitedString(others, COMMA_SEPARATOR); |
| return isEmpty(another) ? one : one + COMMA_SEPARATOR + another; |
| } |
| |
| /** |
| * Test str whether starts with the prefix ignore case. |
| * |
| * @param str |
| * @param prefix |
| * @return |
| */ |
| public static boolean startsWithIgnoreCase(String str, String prefix) { |
| if (str == null || prefix == null || str.length() < prefix.length()) { |
| return false; |
| } |
| // return str.substring(0, prefix.length()).equalsIgnoreCase(prefix); |
| return str.regionMatches(true, 0, prefix, 0, prefix.length()); |
| } |
| } |