/*
	Apache License
	Version 2.0, January 2004
	http://www.apache.org/licenses/
	
	TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
	
	1. Definitions.
	
	"License" shall mean the terms and conditions for use, reproduction, and
	distribution as defined by Sections 1 through 9 of this document.
	
	"Licensor" shall mean the copyright owner or entity authorized by the copyright
	owner that is granting the License.
	
	"Legal Entity" shall mean the union of the acting entity and all other entities
	that control, are controlled by, or are under common control with that entity.
	For the purposes of this definition, "control" means (i) the power, direct or
	indirect, to cause the direction or management of such entity, whether by
	contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
	outstanding shares, or (iii) beneficial ownership of such entity.
	
	"You" (or "Your") shall mean an individual or Legal Entity exercising
	permissions granted by this License.
	
	"Source" form shall mean the preferred form for making modifications, including
	but not limited to software source code, documentation source, and configuration
	files.
	
	"Object" form shall mean any form resulting from mechanical transformation or
	translation of a Source form, including but not limited to compiled object code,
	generated documentation, and conversions to other media types.
	
	"Work" shall mean the work of authorship, whether in Source or Object form, made
	available under the License, as indicated by a copyright notice that is included
	in or attached to the work (an example is provided in the Appendix below).
	
	"Derivative Works" shall mean any work, whether in Source or Object form, that
	is based on (or derived from) the Work and for which the editorial revisions,
	annotations, elaborations, or other modifications represent, as a whole, an
	original work of authorship. For the purposes of this License, Derivative Works
	shall not include works that remain separable from, or merely link (or bind by
	name) to the interfaces of, the Work and Derivative Works thereof.
	
	"Contribution" shall mean any work of authorship, including the original version
	of the Work and any modifications or additions to that Work or Derivative Works
	thereof, that is intentionally submitted to Licensor for inclusion in the Work
	by the copyright owner or by an individual or Legal Entity authorized to submit
	on behalf of the copyright owner. For the purposes of this definition,
	"submitted" means any form of electronic, verbal, or written communication sent
	to the Licensor or its representatives, including but not limited to
	communication on electronic mailing lists, source code control systems, and
	issue tracking systems that are managed by, or on behalf of, the Licensor for
	the purpose of discussing and improving the Work, but excluding communication
	that is conspicuously marked or otherwise designated in writing by the copyright
	owner as "Not a Contribution."
	
	"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
	of whom a Contribution has been received by Licensor and subsequently
	incorporated within the Work.
	
	2. Grant of Copyright License.
	
	Subject to the terms and conditions of this License, each Contributor hereby
	grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
	irrevocable copyright license to reproduce, prepare Derivative Works of,
	publicly display, publicly perform, sublicense, and distribute the Work and such
	Derivative Works in Source or Object form.
	
	3. Grant of Patent License.
	
	Subject to the terms and conditions of this License, each Contributor hereby
	grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
	irrevocable (except as stated in this section) patent license to make, have
	made, use, offer to sell, sell, import, and otherwise transfer the Work, where
	such license applies only to those patent claims licensable by such Contributor
	that are necessarily infringed by their Contribution(s) alone or by combination
	of their Contribution(s) with the Work to which such Contribution(s) was
	submitted. If You institute patent litigation against any entity (including a
	cross-claim or counterclaim in a lawsuit) alleging that the Work or a
	Contribution incorporated within the Work constitutes direct or contributory
	patent infringement, then any patent licenses granted to You under this License
	for that Work shall terminate as of the date such litigation is filed.
	
	4. Redistribution.
	
	You may reproduce and distribute copies of the Work or Derivative Works thereof
	in any medium, with or without modifications, and in Source or Object form,
	provided that You meet the following conditions:
	
	You must give any other recipients of the Work or Derivative Works a copy of
	this License; and
	You must cause any modified files to carry prominent notices stating that You
	changed the files; and
	You must retain, in the Source form of any Derivative Works that You distribute,
	all copyright, patent, trademark, and attribution notices from the Source form
	of the Work, excluding those notices that do not pertain to any part of the
	Derivative Works; and
	If the Work includes a "NOTICE" text file as part of its distribution, then any
	Derivative Works that You distribute must include a readable copy of the
	attribution notices contained within such NOTICE file, excluding those notices
	that do not pertain to any part of the Derivative Works, in at least one of the
	following places: within a NOTICE text file distributed as part of the
	Derivative Works; within the Source form or documentation, if provided along
	with the Derivative Works; or, within a display generated by the Derivative
	Works, if and wherever such third-party notices normally appear. The contents of
	the NOTICE file are for informational purposes only and do not modify the
	License. You may add Your own attribution notices within Derivative Works that
	You distribute, alongside or as an addendum to the NOTICE text from the Work,
	provided that such additional attribution notices cannot be construed as
	modifying the License.
	You may add Your own copyright statement to Your modifications and may provide
	additional or different license terms and conditions for use, reproduction, or
	distribution of Your modifications, or for any such Derivative Works as a whole,
	provided Your use, reproduction, and distribution of the Work otherwise complies
	with the conditions stated in this License.
	
	5. Submission of Contributions.
	
	Unless You explicitly state otherwise, any Contribution intentionally submitted
	for inclusion in the Work by You to the Licensor shall be under the terms and
	conditions of this License, without any additional terms or conditions.
	Notwithstanding the above, nothing herein shall supersede or modify the terms of
	any separate license agreement you may have executed with Licensor regarding
	such Contributions.
	
	6. Trademarks.
	
	This License does not grant permission to use the trade names, trademarks,
	service marks, or product names of the Licensor, except as required for
	reasonable and customary use in describing the origin of the Work and
	reproducing the content of the NOTICE file.
	
	7. Disclaimer of Warranty.
	
	Unless required by applicable law or agreed to in writing, Licensor provides the
	Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
	including, without limitation, any warranties or conditions of TITLE,
	NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
	solely responsible for determining the appropriateness of using or
	redistributing the Work and assume any risks associated with Your exercise of
	permissions under this License.
	
	8. Limitation of Liability.
	
	In no event and under no legal theory, whether in tort (including negligence),
	contract, or otherwise, unless required by applicable law (such as deliberate
	and grossly negligent acts) or agreed to in writing, shall any Contributor be
	liable to You for damages, including any direct, indirect, special, incidental,
	or consequential damages of any character arising as a result of this License or
	out of the use or inability to use the Work (including but not limited to
	damages for loss of goodwill, work stoppage, computer failure or malfunction, or
	any and all other commercial damages or losses), even if such Contributor has
	been advised of the possibility of such damages.
	
	9. Accepting Warranty or Additional Liability.
	
	While redistributing the Work or Derivative Works thereof, You may choose to
	offer, and charge a fee for, acceptance of support, warranty, indemnity, or
	other liability obligations and/or rights consistent with this License. However,
	in accepting such obligations, You may act only on Your own behalf and on Your
	sole responsibility, not on behalf of any other Contributor, and only if You
	agree to indemnify, defend, and hold each Contributor harmless for any liability
	incurred by, or claims asserted against, such Contributor by reason of your
	accepting any such warranty or additional liability.
	
	END OF TERMS AND CONDITIONS
	
	APPENDIX: How to apply the Apache License to your work
	
	To apply the Apache License to your work, attach the following boilerplate
	notice, with the fields enclosed by brackets "{}" replaced with your own
	identifying information. (Don't include the brackets!) The text should be
	enclosed in the appropriate comment syntax for the file format. We also
	recommend that a file or class name and description of purpose be included on
	the same "printed page" as the copyright notice for easier identification within
	third-party archives.
	
	   Copyright 2020 t-io
	
	   Licensed 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.tio.utils.hutool;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;

public class StrUtil {

	public static final int		INDEX_NOT_FOUND	= -1;
	public static final String	EMPTY			= "";

	private static int		cacheSize	= 2048;
	private static String[]	caches		= new String[cacheSize];
	static {
		for (int i = 0; i < cacheSize; i++) {
			caches[i] = String.valueOf(i);
		}
	}

	/**
	 * 用缓存将int转成str
	 * @param data
	 * @return
	 */
	public static String int2Str(int data) {
		if (data < cacheSize) {
			return caches[data];
		} else {
			return String.valueOf(data);
		}
	}

	/**
	 * 去除字符串两边空白符，传入null也返回null
	 * 
	 * @param value 值
	 * @return 去除空白符的值
	 */
	public static String trim(String value) {
		return (null == value) ? null : value.trim();
	}

	/**
	 * 字符串是否为空，空的定义如下:<br>
	 * 1、为null <br>
	 * 2、为""<br>
	 * 
	 * @param str 被检测的字符串
	 * @return 是否为空
	 */
	public static boolean isEmpty(CharSequence str) {
		return str == null || str.length() == 0;
	}

	/**
	 * 字符串是否为非空白 空白的定义如下： <br>
	 * 1、不为null <br>
	 * 2、不为不可见字符（如空格）<br>
	 * 3、不为""<br>
	 * 
	 * @param str 被检测的字符串
	 * @return 是否为非空
	 */
	public static boolean isNotBlank(CharSequence str) {
		return false == isBlank(str);
	}

	/**
	 * 字符串是否为空白 空白的定义如下： <br>
	 * 1、为null <br>
	 * 2、为不可见字符（如空格）<br>
	 * 3、""<br>
	 * 
	 * @param str 被检测的字符串
	 * @return 是否为空
	 */
	public static boolean isBlank(CharSequence str) {
		int length;

		if ((str == null) || ((length = str.length()) == 0)) {
			return true;
		}

		for (int i = 0; i < length; i++) {
			// 只要有一个非空字符即为非空字符串
			if (false == isBlankChar(str.charAt(i))) {
				return false;
			}
		}

		return true;
	}

	/**
	 * 数组或集合转String
	 * 
	 * @param obj 集合或数组对象
	 * @return 数组字符串，与集合转字符串格式相同
	 */
	public static String arrayToString(Object obj) {
		if (null == obj) {
			return null;
		}
		if (isArray(obj)) {
			try {
				return Arrays.deepToString((Object[]) obj);
			} catch (Exception e) {
				final String className = obj.getClass().getComponentType().getName();
				switch (className) {
				case "long":
					return Arrays.toString((long[]) obj);
				case "int":
					return Arrays.toString((int[]) obj);
				case "short":
					return Arrays.toString((short[]) obj);
				case "char":
					return Arrays.toString((char[]) obj);
				case "byte":
					return Arrays.toString((byte[]) obj);
				case "boolean":
					return Arrays.toString((boolean[]) obj);
				case "float":
					return Arrays.toString((float[]) obj);
				case "double":
					return Arrays.toString((double[]) obj);
				default:
					throw new RuntimeException(e);
				}
			}
		}
		return obj.toString();
	}

	/**
	 * 是否以指定字符串开头，忽略大小写
	 * 
	 * @param str 被监测字符串
	 * @param prefix 开头字符串
	 * @return 是否以指定字符串开头
	 */
	public static boolean startWithIgnoreCase(CharSequence str, CharSequence prefix) {
		return startWith(str, prefix, true);
	}

	/**
	 * 是否以指定字符串开头<br>
	 * 如果给定的字符串和开头字符串都为null则返回true，否则任意一个值为null返回false
	 * 
	 * @param str 被监测字符串
	 * @param prefix 开头字符串
	 * @param isIgnoreCase 是否忽略大小写
	 * @return 是否以指定字符串开头
	 */
	public static boolean startWith(CharSequence str, CharSequence prefix, boolean isIgnoreCase) {
		if (null == str || null == prefix) {
			if (null == str && null == prefix) {
				return true;
			}
			return false;
		}

		if (isIgnoreCase) {
			return str.toString().toLowerCase().startsWith(prefix.toString().toLowerCase());
		} else {
			return str.toString().startsWith(prefix.toString());
		}
	}

	/**
	 * 比较两个字符串（大小写敏感）。
	 * 
	 * <pre>
	 * equals(null, null)   = true
	 * equals(null, &quot;abc&quot;)  = false
	 * equals(&quot;abc&quot;, null)  = false
	 * equals(&quot;abc&quot;, &quot;abc&quot;) = true
	 * equals(&quot;abc&quot;, &quot;ABC&quot;) = false
	 * </pre>
	 * 
	 * @param str1 要比较的字符串1
	 * @param str2 要比较的字符串2
	 * 
	 * @return 如果两个字符串相同，或者都是<code>null</code>，则返回<code>true</code>
	 */
	public static boolean equals(CharSequence str1, CharSequence str2) {
		return equals(str1, str2, false);
	}

	/**
	 * 比较两个字符串（大小写不敏感）。
	 * 
	 * <pre>
	 * equalsIgnoreCase(null, null)   = true
	 * equalsIgnoreCase(null, &quot;abc&quot;)  = false
	 * equalsIgnoreCase(&quot;abc&quot;, null)  = false
	 * equalsIgnoreCase(&quot;abc&quot;, &quot;abc&quot;) = true
	 * equalsIgnoreCase(&quot;abc&quot;, &quot;ABC&quot;) = true
	 * </pre>
	 * 
	 * @param str1 要比较的字符串1
	 * @param str2 要比较的字符串2
	 * 
	 * @return 如果两个字符串相同，或者都是<code>null</code>，则返回<code>true</code>
	 */
	public static boolean equalsIgnoreCase(CharSequence str1, CharSequence str2) {
		return equals(str1, str2, true);
	}

	/**
	 * 比较两个字符串是否相等。
	 * 
	 * @param str1 要比较的字符串1
	 * @param str2 要比较的字符串2
	 * @param ignoreCase 是否忽略大小写
	 * @return 如果两个字符串相同，或者都是<code>null</code>，则返回<code>true</code>
	 * @since 3.2.0
	 */
	public static boolean equals(CharSequence str1, CharSequence str2, boolean ignoreCase) {
		if (null == str1) {
			// 只有两个都为null才判断相等
			return str2 == null;
		}
		if (null == str2) {
			// 字符串2空，字符串1非空，直接false
			return false;
		}

		if (ignoreCase) {
			return str1.toString().equalsIgnoreCase(str2.toString());
		} else {
			return str1.equals(str2);
		}
	}

	/**
	 * 将已有字符串填充为规定长度，如果已有字符串超过这个长度则返回这个字符串<br>
	 * 字符填充于字符串后
	 * 
	 * @param str 被填充的字符串
	 * @param filledChar 填充的字符
	 * @param len 填充长度
	 * @return 填充后的字符串
	 * @since 3.1.2
	 */
	public static String fillAfter(String str, char filledChar, int len) {
		return fill(str, filledChar, len, false);
	}

	/**
	 * 将已有字符串填充为规定长度，如果已有字符串超过这个长度则返回这个字符串
	 * 
	 * @param str 被填充的字符串
	 * @param filledChar 填充的字符
	 * @param len 填充长度
	 * @param isPre 是否填充在前
	 * @return 填充后的字符串
	 * @since 3.1.2
	 */
	public static String fill(String str, char filledChar, int len, boolean isPre) {
		final int strLen = str.length();
		if (strLen > len) {
			return str;
		}

		String filledStr = StrUtil.repeat(filledChar, len - strLen);
		return isPre ? filledStr.concat(str) : str.concat(filledStr);
	}

	/**
	 * 重复某个字符
	 * 
	 * @param c 被重复的字符
	 * @param count 重复的数目，如果小于等于0则返回""
	 * @return 重复字符字符串
	 */
	public static String repeat(char c, int count) {
		if (count <= 0) {
			return "";
		}

		char[] result = new char[count];
		for (int i = 0; i < count; i++) {
			result[i] = c;
		}
		return new String(result);
	}

	/**
	 * 除去字符串头部的空白，如果字符串是<code>null</code>，则返回<code>null</code>。
	 * 
	 * <p>
	 * 注意，和<code>String.trim</code>不同，此方法使用<code>CharUtil.isBlankChar</code> 来判定空白， 因而可以除去英文字符集之外的其它空白，如中文空格。
	 * 
	 * <pre>
	 * trimStart(null)         = null
	 * trimStart(&quot;&quot;)           = &quot;&quot;
	 * trimStart(&quot;abc&quot;)        = &quot;abc&quot;
	 * trimStart(&quot;  abc&quot;)      = &quot;abc&quot;
	 * trimStart(&quot;abc  &quot;)      = &quot;abc  &quot;
	 * trimStart(&quot; abc &quot;)      = &quot;abc &quot;
	 * </pre>
	 * 
	 * @param str 要处理的字符串
	 * 
	 * @return 除去空白的字符串，如果原字串为<code>null</code>或结果字符串为<code>""</code>，则返回 <code>null</code>
	 */
	public static String trimStart(CharSequence str) {
		return trim(str, -1);
	}

	/**
	 * 除去字符串尾部的空白，如果字符串是<code>null</code>，则返回<code>null</code>。
	 * 
	 * <p>
	 * 注意，和<code>String.trim</code>不同，此方法使用<code>CharUtil.isBlankChar</code> 来判定空白， 因而可以除去英文字符集之外的其它空白，如中文空格。
	 * 
	 * <pre>
	 * trimEnd(null)       = null
	 * trimEnd(&quot;&quot;)         = &quot;&quot;
	 * trimEnd(&quot;abc&quot;)      = &quot;abc&quot;
	 * trimEnd(&quot;  abc&quot;)    = &quot;  abc&quot;
	 * trimEnd(&quot;abc  &quot;)    = &quot;abc&quot;
	 * trimEnd(&quot; abc &quot;)    = &quot; abc&quot;
	 * </pre>
	 * 
	 * @param str 要处理的字符串
	 * 
	 * @return 除去空白的字符串，如果原字串为<code>null</code>或结果字符串为<code>""</code>，则返回 <code>null</code>
	 */
	public static String trimEnd(CharSequence str) {
		return trim(str, 1);
	}

	/**
	 * 除去字符串头尾部的空白符，如果字符串是<code>null</code>，依然返回<code>null</code>。
	 * 
	 * @param str 要处理的字符串
	 * @param mode <code>-1</code>表示trimStart，<code>0</code>表示trim全部， <code>1</code>表示trimEnd
	 * 
	 * @return 除去指定字符后的的字符串，如果原字串为<code>null</code>，则返回<code>null</code>
	 */
	private static String trim(CharSequence str, int mode) {
		if (str == null) {
			return null;
		}

		int length = str.length();
		int start = 0;
		int end = length;

		// 扫描字符串头部
		if (mode <= 0) {
			while ((start < end) && (isBlankChar(str.charAt(start)))) {
				start++;
			}
		}

		// 扫描字符串尾部
		if (mode >= 0) {
			while ((start < end) && (isBlankChar(str.charAt(end - 1)))) {
				end--;
			}
		}

		if ((start > 0) || (end < length)) {
			return str.toString().substring(start, end);
		}

		return str.toString();
	}

	/**
	 * 对象是否为数组对象
	 * 
	 * @param obj 对象
	 * @return 是否为数组对象，如果为{@code null} 返回false
	 */
	private static boolean isArray(Object obj) {
		if (null == obj) {
			// throw new NullPointerException("Object check for isArray is null");
			return false;
		}
		return obj.getClass().isArray();
	}

	/**
	 * 是否空白符<br>
	 * 空白符包括空格、制表符、全角空格和不间断空格<br>
	 * 
	 * @see Character#isWhitespace(int)
	 * @see Character#isSpaceChar(int)
	 * @param c 字符
	 * @return 是否空白符
	 * @since 4.0.10
	 */
	private static boolean isBlankChar(char c) {
		return Character.isWhitespace(c) || Character.isSpaceChar(c) || c == '\ufeff' || c == '\u202a';
	}

	/**
	 * 指定范围内查找指定字符
	 * 
	 * @param str 字符串
	 * @param searchChar 被查找的字符
	 * @return 位置
	 */
	public static int indexOf(final CharSequence str, char searchChar) {
		return indexOf(str, searchChar, 0);
	}

	/**
	 * 指定范围内查找指定字符
	 * 
	 * @param str 字符串
	 * @param searchChar 被查找的字符
	 * @param start 起始位置，如果小于0，从0开始查找
	 * @return 位置
	 */
	public static int indexOf(final CharSequence str, char searchChar, int start) {
		if (str instanceof String) {
			return ((String) str).indexOf(searchChar, start);
		} else {
			return indexOf(str, searchChar, start, -1);
		}
	}

	/**
	 * 指定范围内查找指定字符
	 * 
	 * @param str 字符串
	 * @param searchChar 被查找的字符
	 * @param start 起始位置，如果小于0，从0开始查找
	 * @param end 终止位置，如果超过str.length()则默认查找到字符串末尾
	 * @return 位置
	 */
	public static int indexOf(final CharSequence str, char searchChar, int start, int end) {
		final int len = str.length();
		if (start < 0 || start > len) {
			start = 0;
		}
		if (end > len || end < 0) {
			end = len;
		}
		for (int i = start; i < end; i++) {
			if (str.charAt(i) == searchChar) {
				return i;
			}
		}
		return -1;
	}

	/**
	 * 指定范围内查找字符串，忽略大小写<br>
	 * 
	 * <pre>
	 * StrUtil.indexOfIgnoreCase(null, *, *)          = -1
	 * StrUtil.indexOfIgnoreCase(*, null, *)          = -1
	 * StrUtil.indexOfIgnoreCase("", "", 0)           = 0
	 * StrUtil.indexOfIgnoreCase("aabaabaa", "A", 0)  = 0
	 * StrUtil.indexOfIgnoreCase("aabaabaa", "B", 0)  = 2
	 * StrUtil.indexOfIgnoreCase("aabaabaa", "AB", 0) = 1
	 * StrUtil.indexOfIgnoreCase("aabaabaa", "B", 3)  = 5
	 * StrUtil.indexOfIgnoreCase("aabaabaa", "B", 9)  = -1
	 * StrUtil.indexOfIgnoreCase("aabaabaa", "B", -1) = 2
	 * StrUtil.indexOfIgnoreCase("aabaabaa", "", 2)   = 2
	 * StrUtil.indexOfIgnoreCase("abc", "", 9)        = -1
	 * </pre>
	 * 
	 * @param str 字符串
	 * @param searchStr 需要查找位置的字符串
	 * @return 位置
	 * @since 3.2.1
	 */
	public static int indexOfIgnoreCase(final CharSequence str, final CharSequence searchStr) {
		return indexOfIgnoreCase(str, searchStr, 0);
	}

	/**
	 * 指定范围内查找字符串
	 * 
	 * <pre>
	 * StrUtil.indexOfIgnoreCase(null, *, *)          = -1
	 * StrUtil.indexOfIgnoreCase(*, null, *)          = -1
	 * StrUtil.indexOfIgnoreCase("", "", 0)           = 0
	 * StrUtil.indexOfIgnoreCase("aabaabaa", "A", 0)  = 0
	 * StrUtil.indexOfIgnoreCase("aabaabaa", "B", 0)  = 2
	 * StrUtil.indexOfIgnoreCase("aabaabaa", "AB", 0) = 1
	 * StrUtil.indexOfIgnoreCase("aabaabaa", "B", 3)  = 5
	 * StrUtil.indexOfIgnoreCase("aabaabaa", "B", 9)  = -1
	 * StrUtil.indexOfIgnoreCase("aabaabaa", "B", -1) = 2
	 * StrUtil.indexOfIgnoreCase("aabaabaa", "", 2)   = 2
	 * StrUtil.indexOfIgnoreCase("abc", "", 9)        = -1
	 * </pre>
	 * 
	 * @param str 字符串
	 * @param searchStr 需要查找位置的字符串
	 * @param fromIndex 起始位置
	 * @return 位置
	 * @since 3.2.1
	 */
	public static int indexOfIgnoreCase(final CharSequence str, final CharSequence searchStr, int fromIndex) {
		return indexOf(str, searchStr, fromIndex, true);
	}

	/**
	 * 指定范围内反向查找字符串
	 * 
	 * @param str 字符串
	 * @param searchStr 需要查找位置的字符串
	 * @param fromIndex 起始位置
	 * @param ignoreCase 是否忽略大小写
	 * @return 位置
	 * @since 3.2.1
	 */
	public static int indexOf(final CharSequence str, CharSequence searchStr, int fromIndex, boolean ignoreCase) {
		if (str == null || searchStr == null) {
			return INDEX_NOT_FOUND;
		}
		if (fromIndex < 0) {
			fromIndex = 0;
		}

		final int endLimit = str.length() - searchStr.length() + 1;
		if (fromIndex > endLimit) {
			return INDEX_NOT_FOUND;
		}
		if (searchStr.length() == 0) {
			return fromIndex;
		}

		if (false == ignoreCase) {
			// 不忽略大小写调用JDK方法
			return str.toString().indexOf(searchStr.toString(), fromIndex);
		}

		for (int i = fromIndex; i < endLimit; i++) {
			if (isSubEquals(str, i, searchStr, 0, searchStr.length(), true)) {
				return i;
			}
		}
		return INDEX_NOT_FOUND;
	}

	/**
	 * 指定范围内查找字符串，忽略大小写<br>
	 * 
	 * @param str 字符串
	 * @param searchStr 需要查找位置的字符串
	 * @return 位置
	 * @since 3.2.1
	 */
	public static int lastIndexOfIgnoreCase(final CharSequence str, final CharSequence searchStr) {
		return lastIndexOfIgnoreCase(str, searchStr, str.length());
	}

	/**
	 * 指定范围内查找字符串，忽略大小写<br>
	 * 
	 * @param str 字符串
	 * @param searchStr 需要查找位置的字符串
	 * @param fromIndex 起始位置，从后往前计数
	 * @return 位置
	 * @since 3.2.1
	 */
	public static int lastIndexOfIgnoreCase(final CharSequence str, final CharSequence searchStr, int fromIndex) {
		return lastIndexOf(str, searchStr, fromIndex, true);
	}

	/**
	 * 指定范围内查找字符串<br>
	 * 
	 * @param str 字符串
	 * @param searchStr 需要查找位置的字符串
	 * @param fromIndex 起始位置，从后往前计数
	 * @param ignoreCase 是否忽略大小写
	 * @return 位置
	 * @since 3.2.1
	 */
	public static int lastIndexOf(final CharSequence str, final CharSequence searchStr, int fromIndex, boolean ignoreCase) {
		if (str == null || searchStr == null) {
			return INDEX_NOT_FOUND;
		}
		if (fromIndex < 0) {
			fromIndex = 0;
		}
		fromIndex = Math.min(fromIndex, str.length());

		if (searchStr.length() == 0) {
			return fromIndex;
		}

		if (false == ignoreCase) {
			// 不忽略大小写调用JDK方法
			return str.toString().lastIndexOf(searchStr.toString(), fromIndex);
		}

		for (int i = fromIndex; i > 0; i--) {
			if (isSubEquals(str, i, searchStr, 0, searchStr.length(), true)) {
				return i;
			}
		}
		return INDEX_NOT_FOUND;
	}

	/**
	 * 截取两个字符串的不同部分（长度一致），判断截取的子串是否相同<br>
	 * 任意一个字符串为null返回false
	 * 
	 * @param str1 第一个字符串
	 * @param start1 第一个字符串开始的位置
	 * @param str2 第二个字符串
	 * @param start2 第二个字符串开始的位置
	 * @param length 截取长度
	 * @param ignoreCase 是否忽略大小写
	 * @return 子串是否相同
	 * @since 3.2.1
	 */
	public static boolean isSubEquals(CharSequence str1, int start1, CharSequence str2, int start2, int length, boolean ignoreCase) {
		if (null == str1 || null == str2) {
			return false;
		}

		return str1.toString().regionMatches(ignoreCase, start1, str2.toString(), start2, length);
	}

	/**
	 * 字符串是否以给定字符开始
	 * 
	 * @param str 字符串
	 * @param c 字符
	 * @return 是否开始
	 */
	public static boolean startWith(CharSequence str, char c) {
		return c == str.charAt(0);
	}

	/**
	 * 是否以指定字符串开头
	 * 
	 * @param str 被监测字符串
	 * @param prefix 开头字符串
	 * @return 是否以指定字符串开头
	 */
	public static boolean startWith(CharSequence str, CharSequence prefix) {
		return startWith(str, prefix, false);
	}

	/**
	 * 字符串是否以给定字符结尾
	 * 
	 * @param str 字符串
	 * @param c 字符
	 * @return 是否结尾
	 */
	public static boolean endWith(CharSequence str, char c) {
		return c == str.charAt(str.length() - 1);
	}

	/**
	 * 是否以指定字符串结尾<br>
	 * 如果给定的字符串和开头字符串都为null则返回true，否则任意一个值为null返回false
	 * 
	 * @param str 被监测字符串
	 * @param suffix 结尾字符串
	 * @param isIgnoreCase 是否忽略大小写
	 * @return 是否以指定字符串结尾
	 */
	public static boolean endWith(CharSequence str, CharSequence suffix, boolean isIgnoreCase) {
		if (null == str || null == suffix) {
			if (null == str && null == suffix) {
				return true;
			}
			return false;
		}

		if (isIgnoreCase) {
			return str.toString().toLowerCase().endsWith(suffix.toString().toLowerCase());
		} else {
			return str.toString().endsWith(suffix.toString());
		}
	}

	/**
	 * 是否以指定字符串结尾
	 * 
	 * @param str 被监测字符串
	 * @param suffix 结尾字符串
	 * @return 是否以指定字符串结尾
	 */
	public static boolean endWith(CharSequence str, CharSequence suffix) {
		return endWith(str, suffix, false);
	}

	/**
	 * 是否以指定字符串结尾，忽略大小写
	 * 
	 * @param str 被监测字符串
	 * @param suffix 结尾字符串
	 * @return 是否以指定字符串结尾
	 */
	public static boolean endWithIgnoreCase(CharSequence str, CharSequence suffix) {
		return endWith(str, suffix, true);
	}

	/**
	 * 指定字符是否在字符串中出现过
	 * 
	 * @param str 字符串
	 * @param searchChar 被查找的字符
	 * @return 是否包含
	 * @since 3.1.2
	 */
	public static boolean contains(CharSequence str, char searchChar) {
		return indexOf(str, searchChar) > -1;
	}

	/**
	 * 是否包含特定字符，忽略大小写，如果给定两个参数都为<code>null</code>，返回true
	 * 
	 * @param str 被检测字符串
	 * @param testStr 被测试是否包含的字符串
	 * @return 是否包含
	 */
	public static boolean containsIgnoreCase(CharSequence str, CharSequence testStr) {
		if (null == str) {
			// 如果被监测字符串和
			return null == testStr;
		}
		return str.toString().toLowerCase().contains(testStr.toString().toLowerCase());
	}

	/**
	 * @param str
	 * @param separator
	 * @return
	 * @author tanyaowu
	 */
	public static String[] split(String str, String separator) {
		if (str == null) {
			return null;
		}

		return str.split(separator);
	}

	/**
	 * 将对象转为字符串<br>
	 * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法
	 * 
	 * @param obj 对象
	 * @return 字符串
	 */
	public static String utf8Str(Object obj) {
		return str(obj, StandardCharsets.UTF_8);
	}

	/**
	 * 将对象转为字符串<br>
	 * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法
	 * 
	 * @param obj 对象
	 * @param charsetName 字符集
	 * @return 字符串
	 */
	public static String str(Object obj, String charsetName) {
		return str(obj, Charset.forName(charsetName));
	}

	/**
	 * 将对象转为字符串<br>
	 * 1、Byte数组和ByteBuffer会被转换为对应字符串的数组 2、对象数组会调用Arrays.toString方法
	 * 
	 * @param obj 对象
	 * @param charset 字符集
	 * @return 字符串
	 */
	public static String str(Object obj, Charset charset) {
		if (null == obj) {
			return null;
		}

		if (obj instanceof String) {
			return (String) obj;
		} else if (obj instanceof byte[]) {
			return str((byte[]) obj, charset);
		} else if (obj instanceof Byte[]) {
			return str((Byte[]) obj, charset);
		} else if (obj instanceof ByteBuffer) {
			return str((ByteBuffer) obj, charset);
		} else if (ArrayUtil.isArray(obj)) {
			return ArrayUtil.toString(obj);
		}

		return obj.toString();
	}

	/**
	 * {@link CharSequence} 转为字符串，null安全
	 * 
	 * @param cs {@link CharSequence}
	 * @return 字符串
	 */
	public static String str(CharSequence cs) {
		return null == cs ? null : cs.toString();
	}

	/**
	 * 改进JDK subString<br>
	 * index从0开始计算，最后一个字符为-1<br>
	 * 如果from和to位置一样，返回 "" <br>
	 * 如果from或to为负数，则按照length从后向前数位置，如果绝对值大于字符串长度，则from归到0，to归到length<br>
	 * 如果经过修正的index中from大于to，则互换from和to example: <br>
	 * abcdefgh 2 3 =》 c <br>
	 * abcdefgh 2 -3 =》 cde <br>
	 * 
	 * @param str String
	 * @param fromIndex 开始的index（包括）
	 * @param toIndex 结束的index（不包括）
	 * @return 字串
	 */
	public static String sub(CharSequence str, int fromIndex, int toIndex) {
		if (isEmpty(str)) {
			return str(str);
		}
		int len = str.length();

		if (fromIndex < 0) {
			fromIndex = len + fromIndex;
			if (fromIndex < 0) {
				fromIndex = 0;
			}
		} else if (fromIndex > len) {
			fromIndex = len;
		}

		if (toIndex < 0) {
			toIndex = len + toIndex;
			if (toIndex < 0) {
				toIndex = len;
			}
		} else if (toIndex > len) {
			toIndex = len;
		}

		if (toIndex < fromIndex) {
			int tmp = fromIndex;
			fromIndex = toIndex;
			toIndex = tmp;
		}

		if (fromIndex == toIndex) {
			return EMPTY;
		}

		return str.toString().substring(fromIndex, toIndex);
	}

	/**
	 * 截取部分字符串，这里一个汉字的长度认为是2
	 *
	 * @param str 字符串
	 * @param len 切割的位置
	 * @param suffix 切割后加上后缀
	 * @return 切割后的字符串
	 * @since 3.1.1
	 */
	public static String subPreGbk(CharSequence str, int len, CharSequence suffix) {
		if (isEmpty(str)) {
			return str(str);
		}

		byte b[];
		int counterOfDoubleByte = 0;
		b = str.toString().getBytes(Charset.forName("GBK"));
		if (b.length <= len) {
			return str.toString();
		}
		for (int i = 0; i < len; i++) {
			if (b[i] < 0) {
				counterOfDoubleByte++;
			}
		}

		if (counterOfDoubleByte % 2 != 0) {
			len += 1;
		}
		return new String(b, 0, len, Charset.forName("GBK")) + suffix;
	}

	/**
	 * 限制字符串长度，如果超过指定长度，截取指定长度并在末尾加"..."
	 * 
	 * @param string 字符串
	 * @param length 最大长度
	 * @return 切割后的剩余的前半部分字符串+"..."
	 * @since 4.0.10
	 */
	public static String maxLength(CharSequence string, int length) {

		if (null == string) {
			return null;
		}
		if (string.length() <= length) {
			return string.toString();
		}
		return sub(string, 0, length) + "...";
	}

	/**
	 * 切割指定位置之前部分的字符串
	 * 
	 * @param string 字符串
	 * @param toIndex 切割到的位置（不包括）
	 * @return 切割后的剩余的前半部分字符串
	 */
	public static String subPre(CharSequence string, int toIndex) {
		return sub(string, 0, toIndex);
	}

	/**
	 * 切割指定位置之后部分的字符串
	 * 
	 * @param string 字符串
	 * @param fromIndex 切割开始的位置（包括）
	 * @return 切割后后剩余的后半部分字符串
	 */
	public static String subSuf(CharSequence string, int fromIndex) {
		if (isEmpty(string)) {
			return null;
		}
		return sub(string, fromIndex, string.length());
	}

	/**
	 * 切割指定长度的后部分的字符串
	 * 
	 * <pre>
	 * StrUtil.subSufByLength("abcde", 3)      =    "cde"
	 * StrUtil.subSufByLength("abcde", 0)      =    ""
	 * StrUtil.subSufByLength("abcde", -5)     =    ""
	 * StrUtil.subSufByLength("abcde", -1)     =    ""
	 * StrUtil.subSufByLength("abcde", 5)       =    "abcde"
	 * StrUtil.subSufByLength("abcde", 10)     =    "abcde"
	 * StrUtil.subSufByLength(null, 3)               =    null
	 * </pre>
	 * 
	 * @param string 字符串
	 * @param length 切割长度
	 * @return 切割后后剩余的后半部分字符串
	 * @since 4.0.1
	 */
	public static String subSufByLength(CharSequence string, int length) {
		if (isEmpty(string)) {
			return null;
		}
		if (length <= 0) {
			return EMPTY;
		}
		return sub(string, -length, string.length());
	}

	/**
	 * 截取字符串,从指定位置开始,截取指定长度的字符串<br>
	 * author weibaohui
	 * 
	 * @param input 原始字符串
	 * @param fromIndex 开始的index,包括
	 * @param length 要截取的长度
	 * @return 截取后的字符串
	 */
	public static String subWithLength(String input, int fromIndex, int length) {
		return sub(input, fromIndex, fromIndex + length);
	}

	/**
	 * 截取分隔字符串之前的字符串，不包括分隔字符串<br>
	 * 如果给定的字符串为空串（null或""）或者分隔字符串为null，返回原字符串<br>
	 * 如果分隔字符串为空串""，则返回空串，如果分隔字符串未找到，返回原字符串
	 * 
	 * 栗子：
	 * 
	 * <pre>
	 * StrUtil.subBefore(null, *)      = null
	 * StrUtil.subBefore("", *)        = ""
	 * StrUtil.subBefore("abc", "a")   = ""
	 * StrUtil.subBefore("abcba", "b") = "a"
	 * StrUtil.subBefore("abc", "c")   = "ab"
	 * StrUtil.subBefore("abc", "d")   = "abc"
	 * StrUtil.subBefore("abc", "")    = ""
	 * StrUtil.subBefore("abc", null)  = "abc"
	 * </pre>
	 * 
	 * @param string 被查找的字符串
	 * @param separator 分隔字符串（不包括）
	 * @param isLastSeparator 是否查找最后一个分隔字符串（多次出现分隔字符串时选取最后一个），true为选取最后一个
	 * @return 切割后的字符串
	 * @since 3.1.1
	 */
	public static String subBefore(CharSequence string, CharSequence separator, boolean isLastSeparator) {
		if (isEmpty(string) || separator == null) {
			return null == string ? null : string.toString();
		}

		final String str = string.toString();
		final String sep = separator.toString();
		if (sep.isEmpty()) {
			return EMPTY;
		}
		final int pos = isLastSeparator ? str.lastIndexOf(sep) : str.indexOf(sep);
		if (pos == INDEX_NOT_FOUND) {
			return str;
		}
		return str.substring(0, pos);
	}

	/**
	 * 截取分隔字符串之后的字符串，不包括分隔字符串<br>
	 * 如果给定的字符串为空串（null或""），返回原字符串<br>
	 * 如果分隔字符串为空串（null或""），则返回空串，如果分隔字符串未找到，返回空串
	 *
	 * 栗子：
	 * 
	 * <pre>
	 * StrUtil.subAfter(null, *)      = null
	 * StrUtil.subAfter("", *)        = ""
	 * StrUtil.subAfter(*, null)      = ""
	 * StrUtil.subAfter("abc", "a")   = "bc"
	 * StrUtil.subAfter("abcba", "b") = "cba"
	 * StrUtil.subAfter("abc", "c")   = ""
	 * StrUtil.subAfter("abc", "d")   = ""
	 * StrUtil.subAfter("abc", "")    = "abc"
	 * </pre>
	 *
	 * @param string 被查找的字符串
	 * @param separator 分隔字符串（不包括）
	 * @param isLastSeparator 是否查找最后一个分隔字符串（多次出现分隔字符串时选取最后一个），true为选取最后一个
	 * @return 切割后的字符串
	 * @since 3.1.1
	 */
	public static String subAfter(CharSequence string, CharSequence separator, boolean isLastSeparator) {
		if (isEmpty(string)) {
			return null == string ? null : string.toString();
		}
		if (separator == null) {
			return EMPTY;
		}
		final String str = string.toString();
		final String sep = separator.toString();
		final int pos = isLastSeparator ? str.lastIndexOf(sep) : str.indexOf(sep);
		if (pos == INDEX_NOT_FOUND) {
			return EMPTY;
		}
		return str.substring(pos + separator.length());
	}

	/**
	 * 截取指定字符串中间部分，不包括标识字符串<br>
	 * 
	 * 栗子：
	 * 
	 * <pre>
	 * StrUtil.subBetween("wx[b]yz", "[", "]") = "b"
	 * StrUtil.subBetween(null, *, *)          = null
	 * StrUtil.subBetween(*, null, *)          = null
	 * StrUtil.subBetween(*, *, null)          = null
	 * StrUtil.subBetween("", "", "")          = ""
	 * StrUtil.subBetween("", "", "]")         = null
	 * StrUtil.subBetween("", "[", "]")        = null
	 * StrUtil.subBetween("yabcz", "", "")     = ""
	 * StrUtil.subBetween("yabcz", "y", "z")   = "abc"
	 * StrUtil.subBetween("yabczyabcz", "y", "z")   = "abc"
	 * </pre>
	 * 
	 * @param str 被切割的字符串
	 * @param before 截取开始的字符串标识
	 * @param after 截取到的字符串标识
	 * @return 截取后的字符串
	 * @since 3.1.1
	 */
	public static String subBetween(CharSequence str, CharSequence before, CharSequence after) {
		if (str == null || before == null || after == null) {
			return null;
		}

		final String str2 = str.toString();
		final String before2 = before.toString();
		final String after2 = after.toString();

		final int start = str2.indexOf(before2);
		if (start != INDEX_NOT_FOUND) {
			final int end = str2.indexOf(after2, start + before2.length());
			if (end != INDEX_NOT_FOUND) {
				return str2.substring(start + before2.length(), end);
			}
		}
		return null;
	}

	/**
	 * 截取指定字符串中间部分，不包括标识字符串<br>
	 * 
	 * 栗子：
	 * 
	 * <pre>
	 * StrUtil.subBetween(null, *)            = null
	 * StrUtil.subBetween("", "")             = ""
	 * StrUtil.subBetween("", "tag")          = null
	 * StrUtil.subBetween("tagabctag", null)  = null
	 * StrUtil.subBetween("tagabctag", "")    = ""
	 * StrUtil.subBetween("tagabctag", "tag") = "abc"
	 * </pre>
	 * 
	 * @param str 被切割的字符串
	 * @param beforeAndAfter 截取开始和结束的字符串标识
	 * @return 截取后的字符串
	 * @since 3.1.1
	 */
	public static String subBetween(CharSequence str, CharSequence beforeAndAfter) {
		return subBetween(str, beforeAndAfter, beforeAndAfter);
	}

	/**
	 * 
	 * @param type
	 * @param value
	 * @return
	 * @throws Exception
	 * @author tanyaowu
	 */
	public static Object convert(Class<?> type, String value) throws Exception {
		if (type == String.class) {
			return value;
		} else {
			if (StrUtil.isBlank(value)) {
				return null;
			}

			if (type == Byte.class || type == byte.class) {
				return Byte.parseByte(value);
			} else if (type == Short.class || type == short.class) {
				return Short.parseShort(value);
			} else if (type == Integer.class || type == int.class) {
				return Integer.parseInt(value);
			} else if (type == Long.class || type == long.class) {
				return Long.parseLong(value);
			} else if (type == Float.class || type == float.class) {
				return Float.parseFloat(value);
			} else if (type == Double.class || type == double.class) {
				return Double.parseDouble(value);
			} else if (type == Character.class || type == char.class) {
				return Character.valueOf(value.charAt(0));
			} else if (type == Boolean.class || type == boolean.class) {
				return "1".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value) || "yes".equalsIgnoreCase(value);
			} else if (type == BigDecimal.class) {
				return new BigDecimal(value);
			} else if (type == BigInteger.class) {
				return new BigInteger(value);
			} else if (type == Number.class) {
				return NumberFormat.getInstance().parse(value);
			} else if (type == Date.class) {
				return DateUtil.parseToDate(value);
			} else if (type == java.sql.Date.class) {
				return DateUtil.parseToSqlDate(value);
			} else if (type == java.sql.Timestamp.class) {
				return DateUtil.parseToTimestamp(value);
			} else if (type == java.sql.Time.class) {
				return DateUtil.parseToTime(value);
			}
		}

		throw new Exception("不知道要转换成啥" + type);
	}

	/**
	 * 
	 * @param type
	 * @param values
	 * @return 返回的也是一个数组
	 * @throws Exception
	 * @author tanyaowu
	 */
	//	@SuppressWarnings("unchecked")
	public static Object convert(Class<?> type, String[] values) throws Exception {
		if (values == null) {
			return null;
		}

		Class<?> componentType = null;
		if (type.isArray()) {
			componentType = type.getComponentType();
		} else {
			componentType = type;
		}

		if (componentType.isPrimitive()) {
			if (componentType == int.class) {
				int[] ret = new int[values.length];
				for (int i = 0; i < ret.length; i++) {
					if (isBlank(values[i])) {
						ret[i] = 0;
						continue;
					}
					ret[i] = Integer.parseInt(values[i]);
				}
				return ret;
			} else if (componentType == long.class) {
				long[] ret = new long[values.length];
				for (int i = 0; i < ret.length; i++) {
					if (isBlank(values[i])) {
						ret[i] = 0;
						continue;
					}
					ret[i] = Long.parseLong(values[i]);
				}
				return ret;
			} else if (componentType == short.class) {
				short[] ret = new short[values.length];
				for (int i = 0; i < ret.length; i++) {
					if (isBlank(values[i])) {
						ret[i] = 0;
						continue;
					}
					ret[i] = Short.parseShort(values[i]);
				}
				return ret;
			} else if (componentType == double.class) {
				double[] ret = new double[values.length];
				for (int i = 0; i < ret.length; i++) {
					if (isBlank(values[i])) {
						ret[i] = 0;
						continue;
					}
					ret[i] = Double.parseDouble(values[i]);
				}
				return ret;
			} else if (componentType == float.class) {
				float[] ret = new float[values.length];
				for (int i = 0; i < ret.length; i++) {
					if (isBlank(values[i])) {
						ret[i] = 0;
						continue;
					}
					ret[i] = Float.parseFloat(values[i]);
				}
				return ret;
			} else if (componentType == byte.class) {
				byte[] ret = new byte[values.length];
				for (int i = 0; i < ret.length; i++) {
					if (isBlank(values[i])) {
						ret[i] = 0;
						continue;
					}
					ret[i] = Byte.parseByte(values[i]);
				}
				return ret;
			} else if (componentType == boolean.class) {
				boolean[] ret = new boolean[values.length];
				for (int i = 0; i < ret.length; i++) {
					if (isBlank(values[i])) {
						ret[i] = false;
						continue;
					}
					ret[i] = "1".equalsIgnoreCase(values[i]) || "true".equalsIgnoreCase(values[i]) || "yes".equalsIgnoreCase(values[i]);
				}
				return ret;
			} else if (componentType == char.class) {
				char[] ret = new char[values.length];
				for (int i = 0; i < ret.length; i++) {
					if (isBlank(values[i])) {
						ret[i] = 0;
						continue;
					}
					ret[i] = Character.valueOf(values[i].charAt(0));
				}
				return ret;
			}
			return null;
		} else {
			Object[] ret = ArrayUtil.newArray(componentType, values.length);
			for (int i = 0; i < ret.length; i++) {
				ret[i] = convert(componentType, values[i]);
			}
			return ret;
		}
	}

	/**
	 * 查找指定字符串是否包含指定字符串列表中的任意一个字符串
	 * 
	 * @param str 指定字符串
	 * @param testStrs 需要检查的字符串数组
	 * @return 是否包含任意一个字符串
	 * @since 3.2.0
	 */
	public static boolean containsAny(CharSequence str, CharSequence... testStrs) {
		return null != getContainsStr(str, testStrs);
	}

	/**
	 * 查找指定字符串是否包含指定字符串列表中的任意一个字符串，如果包含返回找到的第一个字符串
	 * 
	 * @param str 指定字符串
	 * @param testStrs 需要检查的字符串数组
	 * @return 被包含的第一个字符串
	 * @since 3.2.0
	 */
	public static String getContainsStr(CharSequence str, CharSequence... testStrs) {
		if (isEmpty(str) || testStrs == null) {
			return null;
		}
		for (CharSequence checkStr : testStrs) {
			if (str.toString().contains(checkStr)) {
				return checkStr.toString();
			}
		}
		return null;
	}

	/**
	 * 大写首字母<br>
	 * 例如：str = name, return Name
	 * 
	 * @param str 字符串
	 * @return 字符串
	 */
	public static String upperFirst(CharSequence str) {
		if (null == str) {
			return null;
		}
		if (str.length() > 0) {
			char firstChar = str.charAt(0);
			if (Character.isLowerCase(firstChar)) {
				return Character.toUpperCase(firstChar) + subSuf(str, 1);
			}
		}
		return str.toString();
	}

	/**
	 * 小写首字母<br>
	 * 例如：str = Name, return name
	 * 
	 * @param str 字符串
	 * @return 字符串
	 */
	public static String lowerFirst(CharSequence str) {
		if (null == str) {
			return null;
		}
		if (str.length() > 0) {
			char firstChar = str.charAt(0);
			if (Character.isUpperCase(firstChar)) {
				return Character.toLowerCase(firstChar) + subSuf(str, 1);
			}
		}
		return str.toString();
	}

	/**
	 * 以 , 为分隔符将多个对象转换为字符串
	 *
	 * @param coll      集合
	 * @return 连接后的字符串
	 */
	public static String join(Collection<?> coll) {
		return join(coll, ",");
	}

	/**
	 * 以 delimiter 为分隔符将多个对象转换为字符串
	 *
	 * @param coll      集合
	 * @param delimiter 分隔符
	 * @return 连接后的字符串
	 */
	public static String join(Collection<?> coll, CharSequence delimiter) {
		if (coll == null || coll.isEmpty()) {
			return "";
		}
		StringBuilder sb = new StringBuilder();
		Iterator<?> it = coll.iterator();
		while (it.hasNext()) {
			sb.append(it.next());
			if (it.hasNext()) {
				sb.append(delimiter);
			}
		}
		return sb.toString();
	}

}
