ignores = new HashSet<>(defaultSkipUrl);
+
+ if (StringUtils.isNotBlank(consumterIgnoreUrls)) {
+ String[] split = consumterIgnoreUrls.split(StringPool.COMMA);
+ ignores.addAll(Arrays.stream(split).filter(url -> StringUtils.hasText(url.trim())).collect(Collectors.toSet()));
+ }
+
+ return ignores;
+ }
+
+ /**
+ * 返回 true 则不进入 filter, 否则进入 filter
+ *
+ * @param lookupPath lookup path
+ * @param includePatterns include patterns
+ * @param excludePatterns exclude patterns
+ * @param pathMatcher path matcher
+ * @return the boolean
+ * @since 1.5.0
+ */
+ public static boolean matches(String lookupPath,
+ String[] includePatterns,
+ String[] excludePatterns,
+ PathMatcher pathMatcher) {
+ if (!ObjectUtils.isEmpty(excludePatterns)) {
+ for (String pattern : excludePatterns) {
+ if (pathMatcher.match(pattern, lookupPath)) {
+ return true;
+ }
+ }
+ }
+ if (ObjectUtils.isEmpty(includePatterns)) {
+ return false;
+ }
+ for (String pattern : includePatterns) {
+ if (pathMatcher.match(pattern, lookupPath)) {
+ return false;
+ }
+ }
+ return false;
+ }
+}
diff --git a/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/base/utils/AivfoStringPool.java b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/base/utils/StringPool.java
similarity index 56%
rename from band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/base/utils/AivfoStringPool.java
rename to band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/base/utils/StringPool.java
index 1d2490e..c65159f 100644
--- a/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/base/utils/AivfoStringPool.java
+++ b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/base/utils/StringPool.java
@@ -1,131 +1,249 @@
- package com.aivfo.el.base.utils;
+package com.aivfo.el.starter.base.utils;
import lombok.experimental.UtilityClass;
/**
* @author: wangyl
- * @date: 2023/4/18
+ * @date: 2023/5/16
* @description: 静态 String 池
*/
@UtilityClass
-public final class AivfoStringPool {
+public final class StringPool {
- /** AMPERSAND */
+ /**
+ * AMPERSAND
+ */
public static final String AMPERSAND = "&";
- /** AND */
+ /**
+ * AND
+ */
public static final String AND = "and";
- /** AT */
+ /**
+ * AT
+ */
public static final String AT = "@";
- /** ASTERISK */
+ /**
+ * ASTERISK
+ */
public static final String ASTERISK = "*";
- /** STAR */
+ /**
+ * STAR
+ */
public static final String STAR = ASTERISK;
- /** SLASH */
+ /**
+ * SLASH
+ */
public static final String SLASH = "/";
- /** DOUBLE_SLASH */
+ /**
+ * DOUBLE_SLASH
+ */
public static final String DOUBLE_SLASH = "#//";
- /** COLON */
+ /**
+ * COLON
+ */
public static final String COLON = ":";
- /** COMMA */
+ /**
+ * COMMA
+ */
public static final String COMMA = ",";
- /** DASH */
+ /**
+ * DASH
+ */
public static final String DASH = "-";
- /** DOLLAR */
+ /**
+ * DOLLAR
+ */
public static final String DOLLAR = "$";
- /** DOT */
+ /**
+ * DOT
+ */
public static final String DOT = ".";
- /** EMPTY */
+ /**
+ * EMPTY
+ */
public static final String EMPTY = "";
- /** EMPTY_JSON */
+ /**
+ * EMPTY_JSON
+ */
public static final String EMPTY_JSON = "{}";
- /** EQUALS */
+ /**
+ * EQUALS
+ */
public static final String EQUALS = "=";
- /** FALSE */
+ /**
+ * FALSE
+ */
public static final String FALSE = "false";
- /** HASH */
+ /**
+ * HASH
+ */
public static final String HASH = "#";
- /** HAT */
+ /**
+ * HAT
+ */
public static final String HAT = "^";
- /** LEFT_BRACE */
+ /**
+ * LEFT_BRACE
+ */
public static final String LEFT_BRACE = "{";
- /** LEFT_BRACKET */
+ /**
+ * LEFT_BRACKET
+ */
public static final String LEFT_BRACKET = "(";
- /** LEFT_CHEV */
+ /**
+ * LEFT_CHEV
+ */
public static final String LEFT_CHEV = "<";
- /** NEWLINE */
+ /**
+ * NEWLINE
+ */
public static final String NEWLINE = "\n";
- /** N */
+ /**
+ * N
+ */
public static final String N = "n";
- /** NO */
+ /**
+ * NO
+ */
public static final String NO = "no";
- /** NULL */
+ /**
+ * NULL
+ */
public static final String NULL = "null";
- /** OFF */
+ /**
+ * OFF
+ */
public static final String OFF = "off";
- /** ON */
+ /**
+ * ON
+ */
public static final String ON = "on";
- /** PERCENT */
+ /**
+ * PERCENT
+ */
public static final String PERCENT = "%";
- /** PIPE */
+ /**
+ * PIPE
+ */
public static final String PIPE = "|";
- /** PLUS */
+ /**
+ * PLUS
+ */
public static final String PLUS = "+";
- /** QUESTION_MARK */
+ /**
+ * QUESTION_MARK
+ */
public static final String QUESTION_MARK = "?";
- /** EXCLAMATION_MARK */
+ /**
+ * EXCLAMATION_MARK
+ */
public static final String EXCLAMATION_MARK = "!";
- /** QUOTE */
+ /**
+ * QUOTE
+ */
public static final String QUOTE = "\"";
- /** RETURN */
+ /**
+ * RETURN
+ */
public static final String RETURN = "\r";
- /** TAB */
+ /**
+ * TAB
+ */
public static final String TAB = "\t";
- /** RIGHT_BRACE */
+ /**
+ * RIGHT_BRACE
+ */
public static final String RIGHT_BRACE = "}";
- /** RIGHT_BRACKET */
+ /**
+ * RIGHT_BRACKET
+ */
public static final String RIGHT_BRACKET = ")";
- /** RIGHT_CHEV */
+ /**
+ * RIGHT_CHEV
+ */
public static final String RIGHT_CHEV = ">";
- /** SEMICOLON */
+ /**
+ * SEMICOLON
+ */
public static final String SEMICOLON = ";";
- /** SINGLE_QUOTE */
+ /**
+ * SINGLE_QUOTE
+ */
public static final String SINGLE_QUOTE = "'";
- /** BACKTICK */
+ /**
+ * BACKTICK
+ */
public static final String BACKTICK = "`";
- /** SPACE */
+ /**
+ * SPACE
+ */
public static final String SPACE = " ";
- /** TILDA */
+ /**
+ * TILDA
+ */
public static final String TILDA = "~";
- /** LEFT_SQ_BRACKET */
+ /**
+ * LEFT_SQ_BRACKET
+ */
public static final String LEFT_SQ_BRACKET = "[";
- /** RIGHT_SQ_BRACKET */
+ /**
+ * RIGHT_SQ_BRACKET
+ */
public static final String RIGHT_SQ_BRACKET = "]";
- /** TRUE */
+ /**
+ * TRUE
+ */
public static final String TRUE = "true";
- /** UNDERSCORE */
+ /**
+ * UNDERSCORE
+ */
public static final String UNDERSCORE = "_";
-// /** UTF_8 */
-// public static final String UTF_8 = Charsets.UTF_8_NAME;
-// /** GBK */
-// public static final String GBK = Charsets.GBK_NAME;
-// /** ISO_8859_1 */
-// public static final String ISO_8859_1 = Charsets.ISO_8859_1_NAME;
- /** Y */
+ /**
+ * UTF_8
+ */
+ public static final String UTF_8 = Charsets.UTF_8_NAME;
+ /**
+ * GBK
+ */
+ public static final String GBK = Charsets.GBK_NAME;
+ /**
+ * ISO_8859_1
+ */
+ public static final String ISO_8859_1 = Charsets.ISO_8859_1_NAME;
+ /**
+ * Y
+ */
public static final String Y = "y";
- /** YES */
+ /**
+ * YES
+ */
public static final String YES = "yes";
- /** ONE */
+ /**
+ * ONE
+ */
public static final String ONE = "1";
- /** ZERO */
+ /**
+ * ZERO
+ */
public static final String ZERO = "0";
- /** DOLLAR_LEFT_BRACE */
+ /**
+ * DOLLAR_LEFT_BRACE
+ */
public static final String DOLLAR_LEFT_BRACE = "${";
- /** NULL_STRING */
+ /**
+ * NULL_STRING
+ */
public static final String NULL_STRING = "N/A";
- /** any_Url_Patterns */
+ /**
+ * any_Url_Patterns
+ */
public static final String ANY_URL_PATTERNS = SLASH + ASTERISK;
- /** DOUBLE_ASTERISK */
+ /**
+ * DOUBLE_ASTERISK
+ */
public static final String DOUBLE_ASTERISK = ASTERISK + ASTERISK;
- /** any_PATH */
+ /**
+ * any_PATH
+ */
public static final String ANY_PATH = SLASH + DOUBLE_ASTERISK;
}
diff --git a/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/base/utils/StringUtils.java b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/base/utils/StringUtils.java
new file mode 100644
index 0000000..f03c471
--- /dev/null
+++ b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/base/utils/StringUtils.java
@@ -0,0 +1,1826 @@
+package com.aivfo.el.starter.base.utils;
+
+import cn.hutool.core.text.StrSplitter;
+import com.aivfo.el.starter.base.support.StrFormatter;
+import com.aivfo.el.starter.core.util.NumberUtils;
+import org.apache.commons.text.StringSubstitutor;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.springframework.util.ObjectUtils;
+import org.springframework.web.util.HtmlUtils;
+
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.text.MessageFormat;
+import java.util.*;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
+
+/**
+ * @author: wangyl
+ * @date: 2023/5/16
+ * @description: 继承自Spring util的工具类
+ */
+@SuppressWarnings("all")
+public abstract class StringUtils extends org.springframework.util.StringUtils {
+ /**
+ * INDEX_NOT_FOUND
+ */
+ public static final int INDEX_NOT_FOUND = -1;
+ /**
+ * 特殊字符正则,sql特殊字符和空白符
+ */
+ public static final Pattern SPECIAL_CHARS_REGEX = Pattern.compile("[`'\"|/,;()-+*%#·•� \\s]");
+ /**
+ * BLANK_SYMBOL
+ */
+ public static final String BLANK_SYMBOL = "\\s*|\t|\r|\n";
+ /**
+ * 随机字符串因子
+ */
+ public static final String INT_STR = "0123456789";
+ /**
+ * STR_STR
+ */
+ public static final String STR_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ /**
+ * ALL_STR
+ */
+ public static final String ALL_STR = INT_STR + STR_STR;
+
+ /**
+ * 有 任意 一个 Blank
+ *
+ * @param css CharSequence
+ * @return boolean boolean
+ * @since 1.0.0
+ */
+ public static boolean isAnyBlank(CharSequence... css) {
+ if (ObjectUtils.isEmpty(css)) {
+ return true;
+ }
+ return Stream.of(css).anyMatch(StringUtils::isBlank);
+ }
+
+ /**
+ * Check whether the given {@code CharSequence} contains actual text.
+ * More specifically, this method returns {@code true} if the
+ * {@code CharSequence} is not {@code null}, its length is greater than
+ * 0, and it contains at least one non-whitespace character.
+ *
+ * StringUtil.isBlank(null) = true
+ * StringUtil.isBlank("") = true
+ * StringUtil.isBlank(" ") = true
+ * StringUtil.isBlank("12345") = false
+ * StringUtil.isBlank(" 12345 ") = false
+ *
+ *
+ * @param cs the {@code CharSequence} to check (may be {@code null})
+ * @return {@code true} if the {@code CharSequence} is not {@code null}, its length is greater than 0, and it does not contain
+ * whitespace only
+ * @see Character#isWhitespace Character#isWhitespaceCharacter#isWhitespace
+ * @since 1.0.0
+ */
+ @Contract("null -> true")
+ public static boolean isBlank(CharSequence cs) {
+ return !StringUtils.hasText(cs);
+ }
+
+ /**
+ * 是否全部都不是 blank
+ *
+ * @param css CharSequence
+ * @return boolean boolean
+ * @since 1.0.0
+ */
+ public static boolean isNoneBlank(CharSequence... css) {
+ if (ObjectUtils.isEmpty(css)) {
+ return false;
+ }
+ return Stream.of(css).allMatch(StringUtils::isNotBlank);
+ }
+
+ /**
+ * 是否全部都是 blank
+ *
+ * @param css css
+ * @return the boolean
+ * @since 1.0.0
+ */
+ public static boolean isAllBlank(CharSequence... css) {
+ if (ObjectUtils.isEmpty(css)) {
+ return true;
+ }
+ return Stream.of(css).allMatch(StringUtils::isBlank);
+ }
+
+ /**
+ * Checks if a CharSequence is not empty (""), not null and not whitespace only.
+ *
+ * StringUtil.isNotBlank(null) = false
+ * StringUtil.isNotBlank("") = false
+ * StringUtil.isNotBlank(" ") = false
+ * StringUtil.isNotBlank("bob") = true
+ * StringUtil.isNotBlank(" bob ") = true
+ *
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {@code true} if the CharSequence is not empty and not null and not whitespace
+ * @see Character#isWhitespace Character#isWhitespaceCharacter#isWhitespace
+ * @since 1.0.0
+ */
+ @Contract("null -> false")
+ public static boolean isNotBlank(CharSequence cs) {
+ return StringUtils.hasText(cs);
+ }
+
+ /**
+ * 判断一个字符串是否是数字
+ *
+ * @param cs the CharSequence to check, may be null
+ * @return {boolean}
+ * @since 1.0.0
+ */
+ public static boolean isNumeric(CharSequence cs) {
+ if (isBlank(cs)) {
+ return false;
+ }
+ for (int i = cs.length(); --i >= 0; ) {
+ int chr = cs.charAt(i);
+ if (chr < 48 || chr > 57) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Convert a {@code Collection} into a delimited {@code String} (e.g., CSV).
+ * Useful for {@code toString()} implementations.
+ *
+ * @param coll the {@code Collection} to convert
+ * @return the delimited {@code String}
+ * @since 1.0.0
+ */
+ @NotNull
+ public static String join(Collection> coll) {
+ return StringUtils.collectionToCommaDelimitedString(coll);
+ }
+
+ /**
+ * Convert a {@code Collection} into a delimited {@code String} (e.g. CSV).
+ *
Useful for {@code toString()} implementations.
+ *
+ * @param coll the {@code Collection} to convert
+ * @param delim the delimiter to use (typically a ",")
+ * @return the delimited {@code String}
+ * @since 1.0.0
+ */
+ @NotNull
+ public static String join(Collection> coll, String delim) {
+ return StringUtils.collectionToDelimitedString(coll, delim);
+ }
+
+ /**
+ * Convert a {@code String} array into a comma delimited {@code String}
+ * (i.e., CSV).
+ *
Useful for {@code toString()} implementations.
+ *
+ * @param arr the array to display
+ * @return the delimited {@code String}
+ * @since 1.0.0
+ */
+ @NotNull
+ @Contract(pure = true)
+ public static String join(Object[] arr) {
+ return StringUtils.arrayToCommaDelimitedString(arr);
+ }
+
+ /**
+ * Convert a {@code String} array into a delimited {@code String} (e.g. CSV).
+ *
Useful for {@code toString()} implementations.
+ *
+ * @param arr the array to display
+ * @param delim the delimiter to use (typically a ",")
+ * @return the delimited {@code String}
+ * @since 1.0.0
+ */
+ @NotNull
+ @Contract(pure = true)
+ public static String join(Object[] arr, String delim) {
+ return StringUtils.arrayToDelimitedString(arr, delim);
+ }
+
+ /**
+ * 生成uuid
+ *
+ * @return UUID string
+ * @since 1.0.0
+ */
+ @NotNull
+ public static String randomUid() {
+ ThreadLocalRandom random = ThreadLocalRandom.current();
+ return new UUID(random.nextLong(), random.nextLong()).toString().replace(StringPool.DASH, StringPool.EMPTY);
+ }
+
+ /**
+ * 转义HTML用于安全过滤
+ *
+ * @param html html
+ * @return {String}
+ * @since 1.0.0
+ */
+ @NotNull
+ public static String escapeHtml(String html) {
+ return HtmlUtils.htmlEscape(html);
+ }
+
+ /**
+ * 清理字符串,清理出某些不可见字符
+ *
+ * @param txt 字符串
+ * @return {String}
+ * @since 1.0.0
+ */
+ @NotNull
+ @Contract(pure = true)
+ public static String cleanChars(@NotNull String txt) {
+ return txt.replaceAll("[ `·•�\\f\\t\\v\\s]", "");
+ }
+
+
+ /**
+ * 有序的格式化文本,使用{number}做为占位符
+ * 例:
+ * 通常使用: format("this is {0} for {1}", "a", "b") =》 this is a for b
+ *
+ * @param pattern 文本格式
+ * @param arguments 参数
+ * @return 格式化后的文本 string
+ * @since 1.0.0
+ */
+ @NotNull
+ public static String indexedFormat(@NotNull CharSequence pattern, Object... arguments) {
+ return MessageFormat.format(pattern.toString(), arguments);
+ }
+
+ /**
+ * 切分字符串,不去除切分后每个元素两边的空白符,不去除空白项
+ *
+ * @param str 被切分的字符串
+ * @param separator 分隔符字符
+ * @param limit 限制分片数,-1不限制
+ * @return 切分后的集合 list
+ * @since 1.0.0
+ */
+ @Contract("null, _, _ -> new")
+ public static List split(CharSequence str, char separator, int limit) {
+ return split(str, separator, limit, false, false);
+ }
+
+ /**
+ * 切分字符串
+ *
+ * @param str 被切分的字符串
+ * @param separator 分隔符字符
+ * @param limit 限制分片数,-1不限制
+ * @param isTrim 是否去除切分字符串后每个元素两边的空格
+ * @param ignoreEmpty 是否忽略空串
+ * @return 切分后的集合 list
+ * @since 3.0.8
+ */
+ @Contract("null, _, _, _, _ -> new")
+ public static List split(CharSequence str, char separator, int limit, boolean isTrim, boolean ignoreEmpty) {
+ if (null == str) {
+ return new ArrayList<>(0);
+ }
+ return StrSplitter.split(str.toString(), separator, limit, isTrim, ignoreEmpty);
+ }
+
+ /**
+ * 切分字符串,去除切分后每个元素两边的空白符,去除空白项
+ *
+ * @param str 被切分的字符串
+ * @param separator 分隔符字符
+ * @return 切分后的集合 list
+ * @since 3.1.2
+ */
+ @Contract("null, _ -> new")
+ public static List splitTrim(CharSequence str, char separator) {
+ return splitTrim(str, separator, -1);
+ }
+
+ /**
+ * 切分字符串,去除切分后每个元素两边的空白符,去除空白项
+ *
+ * @param str 被切分的字符串
+ * @param separator 分隔符字符
+ * @param limit 限制分片数,-1不限制
+ * @return 切分后的集合 list
+ * @since 3.1.0
+ */
+ @Contract("null, _, _ -> new")
+ private static List splitTrim(CharSequence str, char separator, int limit) {
+ return split(str, separator, limit, true, true);
+ }
+
+ /**
+ * 切分字符串,去除切分后每个元素两边的空白符,去除空白项
+ *
+ * @param str 被切分的字符串
+ * @param separator 分隔符字符
+ * @return 切分后的集合 list
+ * @since 3.2.0
+ */
+ @Contract("null, _ -> new")
+ public static List splitTrim(CharSequence str, CharSequence separator) {
+ return splitTrim(str, separator, -1);
+ }
+
+ /**
+ * 切分字符串,去除切分后每个元素两边的空白符,去除空白项
+ *
+ * @param str 被切分的字符串
+ * @param separator 分隔符字符
+ * @param limit 限制分片数,-1不限制
+ * @return 切分后的集合 list
+ * @since 3.2.0
+ */
+ @Contract("null, _, _ -> new")
+ private static List splitTrim(CharSequence str, CharSequence separator, int limit) {
+ return split(str, separator, limit, true, true);
+ }
+
+ /**
+ * 切分字符串
+ *
+ * @param str 被切分的字符串
+ * @param separator 分隔符字符
+ * @param limit 限制分片数,-1不限制
+ * @param isTrim 是否去除切分字符串后每个元素两边的空格
+ * @param ignoreEmpty 是否忽略空串
+ * @return 切分后的集合 list
+ * @since 3.2.0
+ */
+ @Contract("null, _, _, _, _ -> new")
+ public static List split(CharSequence str, CharSequence separator, int limit, boolean isTrim, boolean ignoreEmpty) {
+ if (null == str) {
+ return new ArrayList<>(0);
+ }
+ String separatorStr = (null == separator) ? null : separator.toString();
+ return StrSplitter.split(str.toString(), separatorStr, limit, isTrim, ignoreEmpty);
+ }
+
+ /**
+ * 切分字符串,不限制分片数量
+ *
+ * @param str 被切分的字符串
+ * @param separator 分隔符字符
+ * @param isTrim 是否去除切分字符串后每个元素两边的空格
+ * @param ignoreEmpty 是否忽略空串
+ * @return 切分后的集合 list
+ * @since 3.0.8
+ */
+ @Contract("null, _, _, _ -> new")
+ public static List split(CharSequence str, char separator, boolean isTrim, boolean ignoreEmpty) {
+ return split(str, separator, 0, isTrim, ignoreEmpty);
+ }
+
+ /**
+ * 切分字符串
+ *
+ * @param str 被切分的字符串
+ * @param separator 分隔符
+ * @return 字符串 string [ ]
+ * @since 1.0.0
+ */
+ @NotNull
+ @Contract("null, _ -> new")
+ public static String[] split(CharSequence str, CharSequence separator) {
+ if (str == null) {
+ return new String[0];
+ }
+
+ String separatorStr = (null == separator) ? null : separator.toString();
+ return StrSplitter.splitToArray(str.toString(), separatorStr, 0, false, false);
+ }
+
+ /**
+ * 根据给定长度,将给定字符串截取为多个部分
+ *
+ * @param str 字符串
+ * @param len 每一个小节的长度
+ * @return 截取后的字符串数组 string [ ]
+ * @see StrSpliter#splitByLength(String, int) StrSpliter#splitByLength(String, int)StrSpliter#splitByLength(String, int)
+ * @since 1.0.0
+ */
+ @Contract("null, _ -> new")
+ public static String[] split(CharSequence str, int len) {
+ if (null == str) {
+ return new String[0];
+ }
+ return StrSplitter.splitByLength(str.toString(), len);
+ }
+
+ /**
+ * 指定字符是否在字符串中出现过
+ *
+ * @param str 字符串
+ * @param searchChar 被查找的字符
+ * @return 是否包含 boolean
+ * @since 3.1.2
+ */
+ public static boolean contains(CharSequence str, char searchChar) {
+ return indexOf(str, searchChar) > -1;
+ }
+
+ /**
+ * 指定范围内查找指定字符
+ *
+ * @param str 字符串
+ * @param searchChar 被查找的字符
+ * @return 位置 int
+ * @since 1.0.0
+ */
+ public static int indexOf(CharSequence str, char searchChar) {
+ return indexOf(str, searchChar, 0);
+ }
+
+ /**
+ * 指定范围内查找指定字符
+ *
+ * @param str 字符串
+ * @param searchChar 被查找的字符
+ * @param start 起始位置,如果小于0,从0开始查找
+ * @return 位置 int
+ * @since 1.0.0
+ */
+ public static int indexOf(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 位置 int
+ * @since 1.0.0
+ */
+ public static int indexOf(@NotNull CharSequence str, char searchChar, int start, int end) {
+ 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;
+ }
+
+ /**
+ * 截取分隔字符串之前的字符串,不包括分隔字符串
+ * 如果给定的字符串为空串 (null或"") 或者分隔字符串为null,返回原字符串
+ * 如果分隔字符串为空串"",则返回空串,如果分隔字符串未找到,返回原字符串
+ *
+ * 栗子:
+ *
+ * StringUtil.subBefore(null, *) = null
+ * StringUtil.subBefore("", *) = ""
+ * StringUtil.subBefore("abc", "a") = ""
+ * StringUtil.subBefore("abcba", "b") = "a"
+ * StringUtil.subBefore("abc", "c") = "ab"
+ * StringUtil.subBefore("abc", "d") = "abc"
+ * StringUtil.subBefore("abc", "") = ""
+ * StringUtil.subBefore("abc", null) = "abc"
+ *
+ *
+ * @param string 被查找的字符串
+ * @param separator 分隔字符串 (不包括)
+ * @param isLastSeparator 是否查找最后一个分隔字符串 (多次出现分隔字符串时选取最后一个) ,true为选取最后一个
+ * @return 切割后的字符串 string
+ * @since 3.1.1
+ */
+ @Nullable
+ public static String subBefore(CharSequence string, CharSequence separator, boolean isLastSeparator) {
+ if (StringUtils.isEmpty(string) || separator == null) {
+ return null == string ? null : string.toString();
+ }
+
+ String str = string.toString();
+ String sep = separator.toString();
+ if (sep.isEmpty()) {
+ return StringPool.EMPTY;
+ }
+ int pos = isLastSeparator ? str.lastIndexOf(sep) : str.indexOf(sep);
+ if (pos == INDEX_NOT_FOUND) {
+ return str;
+ }
+ return str.substring(0, pos);
+ }
+
+ /**
+ * 默认查找最后一个分隔符
+ *
+ * @param string string
+ * @param separator separator
+ * @return the string
+ * @since 1.0.0
+ */
+ public static String subBefore(CharSequence string, CharSequence separator) {
+ return subBefore(string, separator, true);
+ }
+
+ /**
+ * 截取分隔字符串之后的字符串,不包括分隔字符串
+ * 如果给定的字符串为空串 (null或"") ,返回原字符串
+ * 如果分隔字符串为空串 (null或"") ,则返回空串,如果分隔字符串未找到,返回空串
+ *
+ * 栗子:
+ *
+ * StringUtil.subAfter(null, *) = null
+ * StringUtil.subAfter("", *) = ""
+ * StringUtil.subAfter(*, null) = ""
+ * StringUtil.subAfter("abc", "a") = "bc"
+ * StringUtil.subAfter("abcba", "b") = "cba"
+ * StringUtil.subAfter("abc", "c") = ""
+ * StringUtil.subAfter("abc", "d") = ""
+ * StringUtil.subAfter("abc", "") = "abc"
+ *
+ *
+ * @param string 被查找的字符串
+ * @param separator 分隔字符串 (不包括)
+ * @param isLastSeparator 是否查找最后一个分隔字符串 (多次出现分隔字符串时选取最后一个) ,true为选取最后一个
+ * @return 切割后的字符串 string
+ * @since 3.1.1
+ */
+ @Nullable
+ public static String subAfter(CharSequence string, CharSequence separator, boolean isLastSeparator) {
+ if (StringUtils.isEmpty(string)) {
+ return null == string ? null : string.toString();
+ }
+ if (separator == null) {
+ return StringPool.EMPTY;
+ }
+ String str = string.toString();
+ String sep = separator.toString();
+ int pos = isLastSeparator ? str.lastIndexOf(sep) : str.indexOf(sep);
+ if (pos == INDEX_NOT_FOUND) {
+ return StringPool.EMPTY;
+ }
+ return str.substring(pos + separator.length());
+ }
+
+ /**
+ * 默认查找第一个分隔符
+ *
+ * @param string string
+ * @param separator separator
+ * @return the string
+ * @since 1.0.0
+ */
+ public static String subAfter(CharSequence string, CharSequence separator) {
+ return subAfter(string, separator, false);
+ }
+
+ /**
+ * 截取指定字符串中间部分,不包括标识字符串
+ *
+ * 栗子:
+ *
+ * StringUtil.subBetween(null, *) = null
+ * StringUtil.subBetween("", "") = ""
+ * StringUtil.subBetween("", "tag") = null
+ * StringUtil.subBetween("tagabctag", null) = null
+ * StringUtil.subBetween("tagabctag", "") = ""
+ * StringUtil.subBetween("tagabctag", "tag") = "abc"
+ *
+ *
+ * @param str 被切割的字符串
+ * @param beforeAndAfter 截取开始和结束的字符串标识
+ * @return 截取后的字符串 string
+ * @since 3.1.1
+ */
+ @Contract("null, _ -> null; !null, null -> null")
+ public static String subBetween(CharSequence str, CharSequence beforeAndAfter) {
+ return subBetween(str, beforeAndAfter, beforeAndAfter);
+ }
+
+ /**
+ * 截取指定字符串中间部分,不包括标识字符串
+ *
+ * 栗子:
+ *
+ * StringUtil.subBetween("wx[b]yz", "[", "]") = "b"
+ * StringUtil.subBetween(null, *, *) = null
+ * StringUtil.subBetween(*, null, *) = null
+ * StringUtil.subBetween(*, *, null) = null
+ * StringUtil.subBetween("", "", "") = ""
+ * StringUtil.subBetween("", "", "]") = null
+ * StringUtil.subBetween("", "[", "]") = null
+ * StringUtil.subBetween("yabcz", "", "") = ""
+ * StringUtil.subBetween("yabcz", "y", "z") = "abc"
+ * StringUtil.subBetween("yabczyabcz", "y", "z") = "abc"
+ *
+ *
+ * @param str 被切割的字符串
+ * @param before 截取开始的字符串标识
+ * @param after 截取到的字符串标识
+ * @return 截取后的字符串 string
+ * @since 3.1.1
+ */
+ @Contract("null, _, _ -> null; !null, null, _ -> null; !null, !null, null -> null")
+ private static String subBetween(CharSequence str, CharSequence before, CharSequence after) {
+ if (str == null || before == null || after == null) {
+ return null;
+ }
+
+ String str2 = str.toString();
+ String before2 = before.toString();
+ String after2 = after.toString();
+
+ int start = str2.indexOf(before2);
+ if (start != INDEX_NOT_FOUND) {
+ int end = str2.indexOf(after2, start + before2.length());
+ if (end != INDEX_NOT_FOUND) {
+ return str2.substring(start + before2.length(), end);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 去掉指定前缀
+ *
+ * @param str 字符串
+ * @param prefix 前缀
+ * @return 切掉后的字符串 ,若前缀不是 preffix, 返回原字符串
+ * @since 1.0.0
+ */
+ public static String removePrefix(CharSequence str, CharSequence prefix) {
+ if (StringUtils.isEmpty(str) || StringUtils.isEmpty(prefix)) {
+ return StringPool.EMPTY;
+ }
+
+ String str2 = str.toString();
+ if (str2.startsWith(prefix.toString())) {
+ return subSuf(str2, prefix.length());
+ }
+ return str2;
+ }
+
+ /**
+ * 切割指定位置之后部分的字符串
+ *
+ * @param string 字符串
+ * @param fromIndex 切割开始的位置 (包括)
+ * @return 切割后后剩余的后半部分字符串 string
+ * @since 1.0.0
+ */
+ @Nullable
+ public static String subSuf(CharSequence string, int fromIndex) {
+ if (StringUtils.isEmpty(string)) {
+ return StringPool.EMPTY;
+ }
+ return sub(string, fromIndex, string.length());
+ }
+
+ /**
+ * 改进JDK subString
+ * index从0开始计算,最后一个字符为-1
+ * 如果from和to位置一样,返回 ""
+ * 如果from或to为负数,则按照length从后向前数位置,如果绝对值大于字符串长度,则from归到0,to归到length
+ * 如果经过修正的index中from大于to,则互换from和to example:
+ * abcdefgh 2 3 =》 c
+ * abcdefgh 2 -3 =》 cde
+ *
+ * @param str String
+ * @param fromIndex 开始的index (包括)
+ * @param toIndex 结束的index (不包括)
+ * @return 字串 string
+ * @since 1.0.0
+ */
+ public static String sub(CharSequence str, int fromIndex, int toIndex) {
+ if (StringUtils.isEmpty(str)) {
+ return StringPool.EMPTY;
+ }
+ 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 StringPool.EMPTY;
+ }
+
+ return str.toString().substring(fromIndex, toIndex);
+ }
+
+ /**
+ * 忽略大小写去掉指定前缀
+ *
+ * @param str 字符串
+ * @param prefix 前缀
+ * @return 切掉后的字符串 ,若前缀不是 prefix, 返回原字符串
+ * @since 1.0.0
+ */
+ public static String removePrefixIgnoreCase(CharSequence str, CharSequence prefix) {
+ if (StringUtils.isEmpty(str) || StringUtils.isEmpty(prefix)) {
+ return StringPool.EMPTY;
+ }
+
+ String str2 = str.toString();
+ if (str2.toLowerCase().startsWith(prefix.toString().toLowerCase())) {
+ return subSuf(str2, prefix.length());
+ }
+ return str2;
+ }
+
+ /**
+ * 去掉指定后缀,并小写首字母
+ *
+ * @param str 字符串
+ * @param suffix 后缀
+ * @return 切掉后的字符串 ,若后缀不是 suffix, 返回原字符串
+ * @since 1.0.0
+ */
+ @NotNull
+ public static String removeSufAndLowerFirst(CharSequence str, CharSequence suffix) {
+ return firstCharToLower(removeSuffix(str, suffix));
+ }
+
+ /**
+ * 首字母变小写
+ *
+ * @param str 字符串
+ * @return {String}
+ * @since 1.0.0
+ */
+ @NotNull
+ public static String firstCharToLower(@NotNull String str) {
+ char firstChar = str.charAt(0);
+ if (firstChar >= CharPool.UPPER_A && firstChar <= CharPool.UPPER_Z) {
+ char[] arr = str.toCharArray();
+ arr[0] += (CharPool.LOWER_A - CharPool.UPPER_A);
+ return new String(arr);
+ }
+ return str;
+ }
+
+ /**
+ * 去掉指定后缀
+ *
+ * @param str 字符串
+ * @param suffix 后缀
+ * @return 切掉后的字符串 ,若后缀不是 suffix, 返回原字符串
+ * @since 1.0.0
+ */
+ public static String removeSuffix(CharSequence str, CharSequence suffix) {
+ if (StringUtils.isEmpty(str) || StringUtils.isEmpty(suffix)) {
+ return StringPool.EMPTY;
+ }
+
+ String str2 = str.toString();
+ if (str2.endsWith(suffix.toString())) {
+ return subPre(str2, str2.length() - suffix.length());
+ }
+ return str2;
+ }
+
+ /**
+ * 切割指定位置之前部分的字符串
+ *
+ * @param string 字符串
+ * @param toIndex 切割到的位置 (不包括)
+ * @return 切割后的剩余的前半部分字符串 string
+ * @since 1.0.0
+ */
+ private static String subPre(CharSequence string, int toIndex) {
+ return sub(string, 0, toIndex);
+ }
+
+ /**
+ * 忽略大小写去掉指定后缀
+ *
+ * @param str 字符串
+ * @param suffix 后缀
+ * @return 切掉后的字符串 ,若后缀不是 suffix, 返回原字符串
+ * @since 1.0.0
+ */
+ public static String removeSuffixIgnoreCase(CharSequence str, CharSequence suffix) {
+ if (StringUtils.isEmpty(str) || StringUtils.isEmpty(suffix)) {
+ return StringPool.EMPTY;
+ }
+
+ String str2 = str.toString();
+ if (str2.toLowerCase().endsWith(suffix.toString().toLowerCase())) {
+ return subPre(str2, str2.length() - suffix.length());
+ }
+ return str2;
+ }
+
+ /**
+ * 首字母变大写
+ *
+ * @param str 字符串
+ * @return {String}
+ * @since 1.0.0
+ */
+ public static String firstCharToUpper(String str) {
+ if (isBlank(str)) {
+ return str;
+ }
+ char firstChar = str.charAt(0);
+ if (firstChar >= CharPool.LOWER_A && firstChar <= CharPool.LOWER_Z) {
+ char[] arr = str.toCharArray();
+ arr[0] -= (CharPool.LOWER_A - CharPool.UPPER_A);
+ return new String(arr);
+ }
+ return str;
+ }
+
+ /**
+ * 指定范围内查找字符串,忽略大小写
+ *
+ * StringUtil.indexOfIgnoreCase(null, *, *) = -1
+ * StringUtil.indexOfIgnoreCase(*, null, *) = -1
+ * StringUtil.indexOfIgnoreCase("", "", 0) = 0
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "A", 0) = 0
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "B", 0) = 2
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "AB", 0) = 1
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "B", 3) = 5
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "B", 9) = -1
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "B", -1) = 2
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "", 2) = 2
+ * StringUtil.indexOfIgnoreCase("abc", "", 9) = -1
+ *
+ *
+ * @param str 字符串
+ * @param searchStr 需要查找位置的字符串
+ * @return 位置 int
+ * @since 3.2.1
+ */
+ public static int indexOfIgnoreCase(CharSequence str, CharSequence searchStr) {
+ return indexOfIgnoreCase(str, searchStr, 0);
+ }
+
+ /**
+ * 指定范围内查找字符串
+ *
+ * StringUtil.indexOfIgnoreCase(null, *, *) = -1
+ * StringUtil.indexOfIgnoreCase(*, null, *) = -1
+ * StringUtil.indexOfIgnoreCase("", "", 0) = 0
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "A", 0) = 0
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "B", 0) = 2
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "AB", 0) = 1
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "B", 3) = 5
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "B", 9) = -1
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "B", -1) = 2
+ * StringUtil.indexOfIgnoreCase("aabaabaa", "", 2) = 2
+ * StringUtil.indexOfIgnoreCase("abc", "", 9) = -1
+ *
+ *
+ * @param str 字符串
+ * @param searchStr 需要查找位置的字符串
+ * @param fromIndex 起始位置
+ * @return 位置 int
+ * @since 3.2.1
+ */
+ private static int indexOfIgnoreCase(CharSequence str, CharSequence searchStr, int fromIndex) {
+ return indexOf(str, searchStr, fromIndex, true);
+ }
+
+ /**
+ * 指定范围内反向查找字符串
+ *
+ * @param str 字符串
+ * @param searchStr 需要查找位置的字符串
+ * @param fromIndex 起始位置
+ * @param ignoreCase 是否忽略大小写
+ * @return 位置 int
+ * @since 3.2.1
+ */
+ public static int indexOf(CharSequence str, CharSequence searchStr, int fromIndex, boolean ignoreCase) {
+ if (str == null || searchStr == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (fromIndex < 0) {
+ fromIndex = 0;
+ }
+
+ int endLimit = str.length() - searchStr.length() + 1;
+ if (fromIndex > endLimit) {
+ return INDEX_NOT_FOUND;
+ }
+ if (searchStr.length() == 0) {
+ return fromIndex;
+ }
+
+ if (!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;
+ }
+
+ /**
+ * 截取两个字符串的不同部分 (长度一致) ,判断截取的子串是否相同
+ * 任意一个字符串为null返回false
+ *
+ * @param str1 第一个字符串
+ * @param start1 第一个字符串开始的位置
+ * @param str2 第二个字符串
+ * @param start2 第二个字符串开始的位置
+ * @param length 截取长度
+ * @param ignoreCase 是否忽略大小写
+ * @return 子串是否相同 boolean
+ * @since 3.2.1
+ */
+ @SuppressWarnings("checkstyle:ParameterNumber")
+ @Contract("null, _, _, _, _, _ -> false; !null, _, null, _, _, _ -> false")
+ private 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 searchStr 需要查找位置的字符串
+ * @return 位置 int
+ * @since 3.2.1
+ */
+ public static int lastIndexOfIgnoreCase(CharSequence str, CharSequence searchStr) {
+ return lastIndexOfIgnoreCase(str, searchStr, str.length());
+ }
+
+ /**
+ * 指定范围内查找字符串,忽略大小写
+ *
+ * @param str 字符串
+ * @param searchStr 需要查找位置的字符串
+ * @param fromIndex 起始位置,从后往前计数
+ * @return 位置 int
+ * @since 3.2.1
+ */
+ private static int lastIndexOfIgnoreCase(CharSequence str, CharSequence searchStr, int fromIndex) {
+ return lastIndexOf(str, searchStr, fromIndex, true);
+ }
+
+ /**
+ * 指定范围内查找字符串
+ *
+ * @param str 字符串
+ * @param searchStr 需要查找位置的字符串
+ * @param fromIndex 起始位置,从后往前计数
+ * @param ignoreCase 是否忽略大小写
+ * @return 位置 int
+ * @since 3.2.1
+ */
+ private static int lastIndexOf(CharSequence str, 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 (!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;
+ }
+
+ /**
+ * 返回字符串 searchStr 在字符串 str 中第 ordinal 次出现的位置.
+ * 此方法来自: Apache-Commons-Lang
+ *
+ * 栗子 (*代表任意字符) :
+ *
+ * StringUtil.ordinalIndexOf(null, *, *) = -1
+ * StringUtil.ordinalIndexOf(*, null, *) = -1
+ * StringUtil.ordinalIndexOf("", "", *) = 0
+ * StringUtil.ordinalIndexOf("aabaabaa", "a", 1) = 0
+ * StringUtil.ordinalIndexOf("aabaabaa", "a", 2) = 1
+ * StringUtil.ordinalIndexOf("aabaabaa", "b", 1) = 2
+ * StringUtil.ordinalIndexOf("aabaabaa", "b", 2) = 5
+ * StringUtil.ordinalIndexOf("aabaabaa", "ab", 1) = 1
+ * StringUtil.ordinalIndexOf("aabaabaa", "ab", 2) = 4
+ * StringUtil.ordinalIndexOf("aabaabaa", "", 1) = 0
+ * StringUtil.ordinalIndexOf("aabaabaa", "", 2) = 0
+ *
+ *
+ * @param str 被检查的字符串,可以为null
+ * @param searchStr 被查找的字符串,可以为null
+ * @param ordinal 第几次出现的位置
+ * @return 查找到的位置 int
+ * @since 3.2.3
+ */
+ public static int ordinalIndexOf(String str, String searchStr, int ordinal) {
+ if (str == null || searchStr == null || ordinal <= 0) {
+ return INDEX_NOT_FOUND;
+ }
+ if (searchStr.length() == 0) {
+ return 0;
+ }
+ int found = 0;
+ int index = INDEX_NOT_FOUND;
+ do {
+ index = str.indexOf(searchStr, index + 1);
+ if (index < 0) {
+ return index;
+ }
+ found++;
+ } while (found < ordinal);
+ return index;
+ }
+
+ /**
+ * 比较两个字符串 (大小写敏感) .
+ *
+ * equalsIgnoreCase(null, null) = true
+ * equalsIgnoreCase(null, "abc") = false
+ * equalsIgnoreCase("abc", null) = false
+ * equalsIgnoreCase("abc", "abc") = true
+ * equalsIgnoreCase("abc", "ABC") = true
+ *
+ *
+ * @param str1 要比较的字符串1
+ * @param str2 要比较的字符串2
+ * @return 如果两个字符串相同 ,或者都是null,则返回true
+ * @since 1.0.0
+ */
+ @Contract("null, null -> true; null, !null -> false; !null, null -> false")
+ public static boolean equals(CharSequence str1, CharSequence str2) {
+ return equals(str1, str2, false);
+ }
+
+ /**
+ * 比较两个字符串是否相等.
+ *
+ * @param str1 要比较的字符串1
+ * @param str2 要比较的字符串2
+ * @param ignoreCase 是否忽略大小写
+ * @return 如果两个字符串相同 ,或者都是null,则返回true
+ * @since 3.2.0
+ */
+ @Contract("null, null, _ -> true; null, !null, _ -> false; !null, null, _ -> false")
+ 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);
+ }
+ }
+
+ /**
+ * 比较两个字符串 (大小写不敏感) .
+ *
+ * equalsIgnoreCase(null, null) = true
+ * equalsIgnoreCase(null, "abc") = false
+ * equalsIgnoreCase("abc", null) = false
+ * equalsIgnoreCase("abc", "abc") = true
+ * equalsIgnoreCase("abc", "ABC") = true
+ *
+ *
+ * @param str1 要比较的字符串1
+ * @param str2 要比较的字符串2
+ * @return 如果两个字符串相同 ,或者都是null,则返回true
+ * @since 1.0.0
+ */
+ @Contract("null, null -> true; null, !null -> false; !null, null -> false")
+ public static boolean equalsIgnoreCase(CharSequence str1, CharSequence str2) {
+ return equals(str1, str2, true);
+ }
+
+ /**
+ * 创建StringBuilder对象
+ *
+ * @return StringBuilder对象 string builder
+ * @since 1.0.0
+ */
+ @NotNull
+ @Contract(value = " -> new", pure = true)
+ public static StringBuilder builder() {
+ return new StringBuilder();
+ }
+
+ /**
+ * 创建StringBuilder对象
+ *
+ * @param capacity 初始大小
+ * @return StringBuilder对象 string builder
+ * @since 1.0.0
+ */
+ @NotNull
+ @Contract(value = "_ -> new", pure = true)
+ public static StringBuilder builder(int capacity) {
+ return new StringBuilder(capacity);
+ }
+
+ /**
+ * 创建StringBuilder对象
+ *
+ * @param strs 初始字符串列表
+ * @return StringBuilder对象 string builder
+ * @since 1.0.0
+ */
+ public static StringBuilder builder(@NotNull CharSequence... strs) {
+ StringBuilder sb = new StringBuilder();
+ for (CharSequence str : strs) {
+ sb.append(str);
+ }
+ return sb;
+ }
+
+ /**
+ * 创建StringBuilder对象
+ *
+ * @param sb 初始StringBuilder
+ * @param strs 初始字符串列表
+ * @return StringBuilder对象 string builder
+ * @since 1.0.0
+ */
+ @Contract("_, _ -> param1")
+ public static StringBuilder appendBuilder(StringBuilder sb, @NotNull CharSequence... strs) {
+ for (CharSequence str : strs) {
+ sb.append(str);
+ }
+ return sb;
+ }
+
+ /**
+ * 获得StringReader
+ *
+ * @param str 字符串
+ * @return StringReader reader
+ * @since 1.0.0
+ */
+ @Contract("null -> null; !null -> new")
+ public static StringReader getReader(CharSequence str) {
+ if (null == str) {
+ return null;
+ }
+ return new StringReader(str.toString());
+ }
+
+ /**
+ * 获得StringWriter
+ *
+ * @return StringWriter writer
+ * @since 1.0.0
+ */
+ @NotNull
+ @Contract(value = " -> new", pure = true)
+ public static StringWriter getWriter() {
+ return new StringWriter();
+ }
+
+
+ /**
+ * 统计指定内容中包含指定字符的数量
+ *
+ * @param content 内容
+ * @param charForSearch 被统计的字符
+ * @return 包含数量 int
+ * @since 1.0.0
+ */
+ public static int count(CharSequence content, char charForSearch) {
+ int count = 0;
+ if (StringUtils.isEmpty(content)) {
+ return 0;
+ }
+ int contentLength = content.length();
+ for (int i = 0; i < contentLength; i++) {
+ if (charForSearch == content.charAt(i)) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ /**
+ * 下划线转驼峰
+ *
+ * @param para 字符串
+ * @return String string
+ * @since 1.0.0
+ */
+ @NotNull
+ public static String underlineToHump(@NotNull String para) {
+ return toHump(para, "_");
+ }
+
+ /**
+ * 驼峰转下划线
+ *
+ * @param para 字符串
+ * @return String string
+ * @since 1.0.0
+ */
+ @NotNull
+ public static String humpToUnderline(String para) {
+ return fromHump(para, "_");
+ }
+
+ /**
+ * 横线转驼峰
+ *
+ * @param para 字符串
+ * @return String string
+ * @since 1.0.0
+ */
+ @NotNull
+ public static String lineToHump(@NotNull String para) {
+ return toHump(para, "-");
+ }
+
+ /**
+ * 驼峰转横线
+ *
+ * @param para 字符串
+ * @return String string
+ * @since 1.0.0
+ */
+ @NotNull
+ public static String humpToLine(String para) {
+ return fromHump(para, "-");
+ }
+
+ /**
+ * To hump string
+ *
+ * @param para para
+ * @param s2 s 2
+ * @return the string
+ * @since 1.0.0
+ */
+ @NotNull
+ private static String toHump(@NotNull String para, String s2) {
+ StringBuilder result = new StringBuilder();
+ String[] a = para.split(s2);
+ for (String s : a) {
+ if (result.length() == 0) {
+ result.append(s.toLowerCase());
+ } else {
+ result.append(s.substring(0, 1).toUpperCase());
+ result.append(s.substring(1).toLowerCase());
+ }
+ }
+ return result.toString();
+ }
+
+ /**
+ * From hump string
+ *
+ * @param para para
+ * @param s s
+ * @return the string
+ * @since 1.0.0
+ */
+ @NotNull
+ private static String fromHump(String para, String s) {
+ para = firstCharToLower(para);
+ StringBuilder sb = new StringBuilder(para);
+ int temp = 0;
+ for (int i = 0; i < para.length(); i++) {
+ if (Character.isUpperCase(para.charAt(i))) {
+ sb.insert(i + temp, s);
+ temp += 1;
+ }
+ }
+ return sb.toString().toLowerCase();
+ }
+
+ /**
+ * 重复某个字符串到指定长度
+ *
+ * @param str 被重复的字符
+ * @param padLen 指定长度
+ * @return 重复字符字符串 string
+ * @since 4.3.2
+ */
+ @Contract("null, _ -> null")
+ private static String repeatByLength(CharSequence str, int padLen) {
+ if (null == str) {
+ return null;
+ }
+ if (padLen <= 0) {
+ return StringPool.EMPTY;
+ }
+ int strLen = str.length();
+ if (strLen == padLen) {
+ return str.toString();
+ } else if (strLen > padLen) {
+ return subPre(str, padLen);
+ }
+
+ // 重复,直到达到指定长度
+ char[] padding = new char[padLen];
+ for (int i = 0; i < padLen; i++) {
+ padding[i] = str.charAt(i % strLen);
+ }
+ return new String(padding);
+ }
+
+ /**
+ * 补充字符串以满足最小长度
+ *
+ * StrUtil.padPre(null, *, *);//null
+ * StrUtil.padPre("1", 3, "ABC");//"AB1"
+ * StrUtil.padPre("123", 2, "ABC");//"12"
+ *
+ *
+ * @param str 字符串
+ * @param minLength 最小长度
+ * @param padStr 补充的字符
+ * @return 补充后的字符串 string
+ * @since 1.0.0
+ */
+ @Contract("null, _, _ -> null")
+ private static String padPre(CharSequence str, int minLength, CharSequence padStr) {
+ if (null == str) {
+ return null;
+ }
+ int strLen = str.length();
+ if (strLen == minLength) {
+ return str.toString();
+ } else if (strLen > minLength) {
+ return subPre(str, minLength);
+ }
+
+ return repeatByLength(padStr, minLength - strLen).concat(str.toString());
+ }
+
+ /**
+ * 切割指定长度的后部分的字符串
+ *
+ * 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
+ *
+ *
+ * @param string 字符串
+ * @param length 切割长度
+ * @return 切割后后剩余的后半部分字符串 string
+ * @since 4.0.1
+ */
+ @Nullable
+ private static String subSufByLength(CharSequence string, int length) {
+ if (StringUtils.isEmpty(string)) {
+ return null;
+ }
+ if (length <= 0) {
+ return StringPool.EMPTY;
+ }
+ return sub(string, -length, string.length());
+ }
+
+ /**
+ * 补充字符串以满足最小长度
+ *
+ * StrUtil.padAfter(null, *, *);//null
+ * StrUtil.padAfter("1", 3, "ABC");//"1AB"
+ * StrUtil.padAfter("123", 2, "ABC");//"23"
+ *
+ *
+ * @param str 字符串,如果为null,按照空串处理
+ * @param minLength 最小长度
+ * @param padStr 补充的字符
+ * @return 补充后的字符串 string
+ * @since 4.3.2
+ */
+ @Contract("null, _, _ -> null")
+ public static String padAfter(CharSequence str, int minLength, CharSequence padStr) {
+ if (null == str) {
+ return null;
+ }
+ int strLen = str.length();
+ if (strLen == minLength) {
+ return str.toString();
+ } else if (strLen > minLength) {
+ return subSufByLength(str, minLength);
+ }
+
+ return str.toString().concat(repeatByLength(padStr, minLength - strLen));
+ }
+
+ /**
+ * {@link CharSequence} 转为字符串,null安全
+ *
+ * @param cs {@link CharSequence}
+ * @return 字符串 string
+ * @since 1.0.0
+ */
+ @Contract("null -> null; !null -> !null")
+ public static String str(CharSequence cs) {
+ return null == cs ? null : cs.toString();
+ }
+
+ /**
+ * 居中字符串,两边补充指定字符串,如果指定长度小于字符串,则返回原字符串
+ *
+ * StrUtil.center(null, *, *) = null
+ * StrUtil.center("", 4, " ") = " "
+ * StrUtil.center("ab", -1, " ") = "ab"
+ * StrUtil.center("ab", 4, " ") = " ab "
+ * StrUtil.center("abcd", 2, " ") = "abcd"
+ * StrUtil.center("a", 4, " ") = " a "
+ * StrUtil.center("a", 4, "yz") = "yayz"
+ * StrUtil.center("abc", 7, null) = " abc "
+ * StrUtil.center("abc", 7, "") = " abc "
+ *
+ *
+ * @param str 字符串
+ * @param size 指定长度
+ * @param padStr 两边补充的字符串
+ * @return 补充后的字符串 string
+ * @since 1.0.0
+ */
+ public static String center(CharSequence str, int size, CharSequence padStr) {
+ if (str == null || size <= 0) {
+ return str(str);
+ }
+ if (StringUtils.isEmpty(padStr)) {
+ padStr = StringPool.SPACE;
+ }
+ int strLen = str.length();
+ int pads = size - strLen;
+ if (pads <= 0) {
+ return str.toString();
+ }
+ str = padPre(str, strLen + pads / 2, padStr);
+ str = padAfter(str, size, padStr);
+ return str.toString();
+ }
+
+ /**
+ * 清理字符串,清理出某些不可见字符和一些sql特殊字符
+ *
+ * @param txt 文本
+ * @return {String}
+ * @since 1.0.0
+ */
+ @Contract("null -> null")
+ @Nullable
+ public static String cleanText(@Nullable String txt) {
+ if (txt == null) {
+ return null;
+ }
+ return SPECIAL_CHARS_REGEX.matcher(txt).replaceAll(StringPool.EMPTY);
+ }
+
+ /**
+ * 获取标识符,用于参数清理
+ *
+ * @param param 参数
+ * @return 清理后的标识符 string
+ * @since 1.0.0
+ */
+ @Contract("null -> null")
+ @Nullable
+ public static String cleanIdentifier(@Nullable String param) {
+ if (param == null) {
+ return null;
+ }
+ StringBuilder paramBuilder = new StringBuilder();
+ for (int i = 0; i < param.length(); i++) {
+ char c = param.charAt(i);
+ if (Character.isJavaIdentifierPart(c)) {
+ paramBuilder.append(c);
+ }
+ }
+ return paramBuilder.toString();
+ }
+
+ /**
+ * 删除空白符
+ *
+ * @param str the str
+ * @return the string
+ * @since 1.0.0
+ */
+ public static String replaceBlank(String str) {
+ String dest = "";
+ if (str != null) {
+ Pattern p = Pattern.compile(BLANK_SYMBOL);
+ Matcher m = p.matcher(str);
+ dest = m.replaceAll("");
+ }
+ return dest;
+ }
+
+ /**
+ * 生成uuid,采用 jdk 9 的形式,优化性能
+ *
+ * @return UUID uuid
+ * @since 1.0.0
+ */
+ @NotNull
+ @Contract(" -> new")
+ public static String getUid() {
+ ThreadLocalRandom random = ThreadLocalRandom.current();
+ long lsb = random.nextLong();
+ long msb = random.nextLong();
+ char[] buf = new char[32];
+ formatUnsignedLong(lsb, buf, 20, 12);
+ formatUnsignedLong(lsb >>> 48, buf, 16, 4);
+ formatUnsignedLong(msb, buf, 12, 4);
+ formatUnsignedLong(msb >>> 16, buf, 8, 4);
+ formatUnsignedLong(msb >>> 32, buf, 0, 8);
+ return new String(buf);
+ }
+
+ /**
+ * Format unsigned long *
+ *
+ * @param val val
+ * @param buf buf
+ * @param offset offset
+ * @param len len
+ * @since 1.0.0
+ */
+ private static void formatUnsignedLong(long val, @NotNull char @NotNull [] buf, int offset, int len) {
+ int charPos = offset + len;
+ int radix = 1 << 4;
+ int mask = radix - 1;
+ do {
+ buf[--charPos] = NumberUtils.DIGITS[((int) val) & mask];
+ val >>>= 4;
+ } while (charPos > offset);
+ }
+
+ /**
+ * 格式化文本, {} 表示占位符
+ * 此方法只是简单将占位符 {} 按照顺序替换为参数
+ * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
+ * 例:
+ * 通常使用: format("this is {} for {}", "a", "b") =》 this is a for b
+ * 转义{}: format("this is \\{} for {}", "a", "b") =》 this is \{} for a
+ * 转义\: format("this is \\\\{} for {}", "a", "b") =》 this is \a for b
+ *
+ * @param template 文本模板,被替换的部分用 {} 表示
+ * @param params 参数值
+ * @return 格式化后的文本 string
+ * @since 1.0.0
+ */
+ @Contract("null, _ -> null")
+ public static String format(CharSequence template, Object... params) {
+ if (null == template) {
+ return null;
+ }
+ return StrFormatter.format(template.toString(), params);
+ }
+
+ /**
+ * 格式化文本,使用 {varName} 占位
+ * map = {a: "aValue", b: "bValue"} format("{a} and {b}", map) ---=》 aValue and bValue
+ *
+ * @param template 文本模板,被替换的部分用 {key} 表示
+ * @param map 参数值对
+ * @return 格式化后的文本 string
+ * @since 1.0.0
+ */
+ @Contract("null, _ -> null; !null, null -> !null")
+ public static String format(CharSequence template, Map, ?> map) {
+ if (null == template) {
+ return null;
+ }
+ if (null == map || map.isEmpty()) {
+ return template.toString();
+ }
+
+ String template2 = template.toString();
+ for (Map.Entry, ?> entry : map.entrySet()) {
+ template2 = template2.replace("{" + entry.getKey() + "}", toStr(entry.getValue()));
+ }
+ return template2;
+ }
+
+ /**
+ * 强转string,并去掉多余空格
+ *
+ * @param str 字符串
+ * @return String string
+ * @since 1.0.0
+ */
+ @Contract(pure = true)
+ public static String toStr(Object str) {
+ return toStr(str, "");
+ }
+
+ /**
+ * 强转string,并去掉多余空格
+ *
+ * @param str 字符串
+ * @param defaultValue 默认值
+ * @return String string
+ * @since 1.0.0
+ */
+ @Contract(value = "null, _ -> param2", pure = true)
+ public static String toStr(Object str, String defaultValue) {
+ return StrFormatter.toStr(str, defaultValue);
+ }
+
+ /**
+ * 查找指定字符串是否包含指定字符串列表中的任意一个字符串
+ *
+ * @param str 指定字符串
+ * @param testStrs 需要检查的字符串数组
+ * @return 是否包含任意一个字符串 boolean
+ * @since 3.2.0
+ */
+ public static boolean containsAny(CharSequence str, CharSequence... testStrs) {
+ return null != getContainsStr(str, testStrs);
+ }
+
+ /**
+ * 查找指定字符串是否包含指定字符串列表中的任意一个字符串,如果包含返回找到的第一个字符串
+ *
+ * @param str 指定字符串
+ * @param testStrs 需要检查的字符串数组
+ * @return 被包含的第一个字符串 contains str
+ * @since 3.2.0
+ */
+ @Nullable
+ private static String getContainsStr(CharSequence str, CharSequence... testStrs) {
+ if (isEmpty(str)) {
+ return null;
+ }
+ for (CharSequence checkStr : testStrs) {
+ if (str.toString().contains(checkStr)) {
+ return checkStr.toString();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 查找指定字符串是否包含指定字符串列表中的任意一个字符串
+ * 忽略大小写
+ *
+ * @param str 指定字符串
+ * @param testStrs 需要检查的字符串数组
+ * @return 是否包含任意一个字符串 boolean
+ * @since 3.2.0
+ */
+ public static boolean containsAnyIgnoreCase(CharSequence str, CharSequence... testStrs) {
+ return null != getContainsStrIgnoreCase(str, testStrs);
+ }
+
+ /**
+ * 查找指定字符串是否包含指定字符串列表中的任意一个字符串,如果包含返回找到的第一个字符串
+ * 忽略大小写
+ *
+ * @param str 指定字符串
+ * @param testStrs 需要检查的字符串数组
+ * @return 被包含的第一个字符串 contains str ignore case
+ * @since 3.2.0
+ */
+ @Nullable
+ private static String getContainsStrIgnoreCase(CharSequence str, CharSequence... testStrs) {
+ if (isEmpty(str)) {
+ return null;
+ }
+ for (CharSequence testStr : testStrs) {
+ if (containsIgnoreCase(str, testStr)) {
+ return testStr.toString();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * 是否包含特定字符,忽略大小写,如果给定两个参数都为null,返回true
+ *
+ * @param str 被检测字符串
+ * @param testStr 被测试是否包含的字符串
+ * @return 是否包含 boolean
+ * @since 1.0.0
+ */
+ @Contract("null, null -> true; null, !null -> false")
+ private static boolean containsIgnoreCase(CharSequence str, CharSequence testStr) {
+ if (null == str) {
+ // 如果被监测字符串和
+ return null == testStr;
+ }
+ return str.toString().toLowerCase().contains(testStr.toString().toLowerCase());
+ }
+
+ /**
+ * Replace string
+ *
+ * @param source source
+ * @param parameter parameter
+ * @return the string
+ * @since 1.0.0
+ */
+ public static String replace(String source, Map parameter) {
+ return replace(source,
+ parameter,
+ StringSubstitutor.DEFAULT_VAR_START,
+ StringSubstitutor.DEFAULT_VAR_END,
+ true);
+ }
+
+
+ /**
+ * 替换
+ *
+ * @param source 源内容
+ * @param parameter 占位符参数
+ * @param prefix 占位符前缀 例如:${
+ * @param suffix 占位符后缀 例如:}
+ * @param enableSubstitutionInVariables 是否在变量名称中进行替换 例如:${system-${版本}} 转义符默认为'$'.
+ * 如果这个字符放在一个变量引用之前, 这个引用将被忽略, 不会被替换 如$${a}将直接输出${a}
+ * @return string string
+ * @since 1.0.0
+ */
+ public static String replace(String source,
+ Map parameter,
+ String prefix,
+ String suffix,
+ boolean enableSubstitutionInVariables) {
+
+ StringSubstitutor strSubstitutor = new StringSubstitutor(parameter, prefix, suffix);
+ strSubstitutor.setEnableSubstitutionInVariables(enableSubstitutionInVariables);
+ return strSubstitutor.replace(source);
+ }
+
+}
+
diff --git a/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/base/utils/TimeoutUtils.java b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/base/utils/TimeoutUtils.java
new file mode 100644
index 0000000..15e6ef2
--- /dev/null
+++ b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/base/utils/TimeoutUtils.java
@@ -0,0 +1,100 @@
+package com.aivfo.el.starter.base.utils;
+
+import com.aivfo.el.starter.base.exception.TimeoutUtilsExecuteException;
+import lombok.experimental.UtilityClass;
+import lombok.extern.slf4j.Slf4j;
+import org.jetbrains.annotations.Contract;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.concurrent.*;
+
+/**
+ * @author: wangyl
+ * @date: 2023/5/16
+ * @description: 简单超时工具类
+ */
+@Slf4j
+@UtilityClass
+@SuppressWarnings("PMD.ThreadPoolCreationRule")
+public final class TimeoutUtils {
+ /**
+ * executor
+ */
+ private static final ExecutorService TIMEOUT_EXECUTOR = Executors.newSingleThreadExecutor();
+
+ /**
+ * Process
+ *
+ * @param parameter
+ * @param task task
+ * @param timeout timeout
+ * @return the t
+ * @throws TimeoutUtilsExecuteException timeout utils execute exception
+ * @since 1.5.0
+ */
+ @Contract("null, _ -> null")
+ public static T process(Callable task, long timeout) throws Exception {
+ return process(task, timeout, TimeUnit.SECONDS);
+ }
+
+ /**
+ * Process
+ *
+ * @param parameter
+ * @param task task
+ * @param timeout timeout
+ * @param unit unit
+ * @return the t
+ * @throws TimeoutUtilsExecuteException timeout utils execute exception
+ * @since 1.7.1
+ */
+ @Contract("null, _, _ -> null")
+ public static T process(Callable task, long timeout, TimeUnit unit) throws Exception {
+ return process(TIMEOUT_EXECUTOR, task, timeout, unit);
+ }
+
+ /**
+ * Process
+ * todo-dong4j : (2021.09.18 16:17) [executorService.submit(TtlCallable.get(task))]
+ *
+ * @param parameter
+ * @param executorService executor service
+ * @param task task
+ * @param timeout timeout
+ * @param unit unit
+ * @return the t
+ * @throws TimeoutUtilsExecuteException timeout utils execute exception
+ * @since 1.7.1
+ */
+ @Contract("_, null, _, _ -> null")
+ public static T process(ExecutorService executorService,
+ Callable task,
+ long timeout,
+ TimeUnit unit) throws Exception {
+ if (task == null) {
+ return null;
+ }
+ Future futureRet = executorService.submit(task);
+ try {
+ return futureRet.get(timeout, unit);
+ } catch (InterruptedException e) {
+ log.error("任务被中断: {}", e.getMessage());
+ throw e;
+ } catch (ExecutionException e) {
+ // 任务抛出异常时将被包装为 ExecutionException
+ Throwable cause = e.getCause();
+ if (cause instanceof InvocationTargetException) {
+ // 如果通过反射调用将会抛出 InvocationTargetException, 这里抛出原始异常
+ throw (InvocationTargetException) cause;
+ }
+ // 否则抛出 ExecutionException, 由业务端处理
+ throw e;
+ } catch (TimeoutException e) {
+ if (!futureRet.isCancelled()) {
+ futureRet.cancel(true);
+ }
+ }
+
+ throw new TimeoutUtilsExecuteException("process timeout: [" + timeout + " " + unit.name().toLowerCase() + "]");
+ }
+}
diff --git a/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/function/CheckedCallable.java b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/function/CheckedCallable.java
new file mode 100644
index 0000000..4477f59
--- /dev/null
+++ b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/function/CheckedCallable.java
@@ -0,0 +1,16 @@
+package com.aivfo.el.starter.core.function;
+
+
+@FunctionalInterface
+@SuppressWarnings("java:S112")
+public interface CheckedCallable {
+
+ /**
+ * Run this callable.
+ *
+ * @return result t
+ * @throws Throwable CheckedException
+ * @since 1.0.0
+ */
+ T call() throws Throwable;
+}
diff --git a/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/core/function/CheckedComparator.java b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/function/CheckedComparator.java
similarity index 53%
rename from band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/core/function/CheckedComparator.java
rename to band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/function/CheckedComparator.java
index 2c46789..cfe5b39 100644
--- a/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/core/function/CheckedComparator.java
+++ b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/function/CheckedComparator.java
@@ -1,16 +1,5 @@
-package com.aivfo.el.core.function;
+package com.aivfo.el.starter.core.function;
-/**
- * Company: 成都返空汇网络技术有限公司
- * Description: 受检的 Comparator
- *
- * @param parameter
- * @author dong4j
- * @version 1.2.3
- * @email "mailto:dongshijie@fkhwl.com"
- * @date 2020.01.27 14:55
- * @since 1.0.0
- */
@FunctionalInterface
@SuppressWarnings("java:S112")
public interface CheckedComparator {
diff --git a/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/function/CheckedConsumer.java b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/function/CheckedConsumer.java
new file mode 100644
index 0000000..cb98cb1
--- /dev/null
+++ b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/function/CheckedConsumer.java
@@ -0,0 +1,16 @@
+package com.aivfo.el.starter.core.function;
+
+@FunctionalInterface
+@SuppressWarnings("java:S112")
+public interface CheckedConsumer {
+
+ /**
+ * Run the Consumer
+ *
+ * @param t T
+ * @throws Throwable UncheckedException
+ * @since 1.0.0
+ */
+ void accept(T t) throws Throwable;
+
+}
diff --git a/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/core/function/CheckedFunction.java b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/function/CheckedFunction.java
similarity index 52%
rename from band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/core/function/CheckedFunction.java
rename to band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/function/CheckedFunction.java
index 70c25e9..18088a8 100644
--- a/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/core/function/CheckedFunction.java
+++ b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/function/CheckedFunction.java
@@ -1,19 +1,7 @@
-package com.aivfo.el.core.function;
+package com.aivfo.el.starter.core.function;
import org.springframework.lang.Nullable;
-/**
- * Company: 成都返空汇网络技术有限公司
- * Description: 受检的 function
- *
- * @param parameter
- * @param parameter
- * @author dong4j
- * @version 1.2.3
- * @email "mailto:dongshijie@fkhwl.com"
- * @date 2020.01.27 14:55
- * @since 1.0.0
- */
@FunctionalInterface
@SuppressWarnings("java:S112")
public interface CheckedFunction {
diff --git a/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/function/CheckedRunnable.java b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/function/CheckedRunnable.java
new file mode 100644
index 0000000..f0e23ed
--- /dev/null
+++ b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/function/CheckedRunnable.java
@@ -0,0 +1,15 @@
+package com.aivfo.el.starter.core.function;
+
+@FunctionalInterface
+@SuppressWarnings("java:S112")
+public interface CheckedRunnable {
+
+ /**
+ * Run this runnable.
+ *
+ * @throws Throwable CheckedException
+ * @since 1.0.0
+ */
+ void run() throws Throwable;
+
+}
diff --git a/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/core/function/CheckedSupplier.java b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/function/CheckedSupplier.java
similarity index 51%
rename from band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/core/function/CheckedSupplier.java
rename to band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/function/CheckedSupplier.java
index b492cc0..0b65972 100644
--- a/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/core/function/CheckedSupplier.java
+++ b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/function/CheckedSupplier.java
@@ -1,18 +1,7 @@
-package com.aivfo.el.core.function;
+package com.aivfo.el.starter.core.function;
import org.springframework.lang.Nullable;
-/**
- * Company: 成都返空汇网络技术有限公司
- * Description: 受检的 Supplier
- *
- * @param parameter
- * @author dong4j
- * @version 1.2.3
- * @email "mailto:dongshijie@fkhwl.com"
- * @date 2020.01.27 14:54
- * @since 1.0.0
- */
@FunctionalInterface
@SuppressWarnings("java:S112")
public interface CheckedSupplier {
diff --git a/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/util/AopTargetUtils.java b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/util/AopTargetUtils.java
new file mode 100644
index 0000000..a74fd9c
--- /dev/null
+++ b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/util/AopTargetUtils.java
@@ -0,0 +1,75 @@
+package com.aivfo.el.starter.core.util;
+
+import lombok.experimental.UtilityClass;
+import org.jetbrains.annotations.NotNull;
+import org.springframework.aop.framework.AdvisedSupport;
+import org.springframework.aop.framework.AopProxy;
+import org.springframework.aop.support.AopUtils;
+
+import java.lang.reflect.Field;
+
+@UtilityClass
+@SuppressWarnings("all")
+public class AopTargetUtils {
+
+ /**
+ * 获取 目标对象
+ *
+ * @param proxy 代理对象
+ * @return target
+ * @throws Exception exception
+ * @since 1.6.0
+ */
+ public static Object getTarget(Object proxy) throws Exception {
+
+ if (!AopUtils.isAopProxy(proxy)) {
+ return proxy;
+ }
+
+ if (AopUtils.isJdkDynamicProxy(proxy)) {
+ return getJdkDynamicProxyTargetObject(proxy);
+ } else {
+ return getCglibProxyTargetObject(proxy);
+ }
+
+ }
+
+ /**
+ * Gets cglib proxy target object *
+ *
+ * @param proxy proxy
+ * @return the cglib proxy target object
+ * @throws Exception exception
+ * @since 1.6.0
+ */
+ private static Object getCglibProxyTargetObject(@NotNull Object proxy) throws Exception {
+ Field h = proxy.getClass().getDeclaredField("CGLIB$CALLBACK_0");
+ h.setAccessible(true);
+ Object dynamicAdvisedInterceptor = h.get(proxy);
+
+ Field advised = dynamicAdvisedInterceptor.getClass().getDeclaredField("advised");
+ advised.setAccessible(true);
+
+ return ((AdvisedSupport) advised.get(dynamicAdvisedInterceptor)).getTargetSource().getTarget();
+ }
+
+ /**
+ * Gets jdk dynamic proxy target object *
+ *
+ * @param proxy proxy
+ * @return the jdk dynamic proxy target object
+ * @throws Exception exception
+ * @since 1.6.0
+ */
+ private static Object getJdkDynamicProxyTargetObject(@NotNull Object proxy) throws Exception {
+ Field h = proxy.getClass().getSuperclass().getDeclaredField("h");
+ h.setAccessible(true);
+ AopProxy aopProxy = (AopProxy) h.get(proxy);
+
+ Field advised = aopProxy.getClass().getDeclaredField("advised");
+ advised.setAccessible(true);
+
+ return ((AdvisedSupport) advised.get(aopProxy)).getTargetSource().getTarget();
+ }
+
+}
diff --git a/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/util/CIDRUtils.java b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/util/CIDRUtils.java
new file mode 100644
index 0000000..f48c8ff
--- /dev/null
+++ b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/util/CIDRUtils.java
@@ -0,0 +1,177 @@
+package com.aivfo.el.starter.core.util;
+
+import org.jetbrains.annotations.NotNull;
+
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A class that enables to get an IP range from CIDR specification. It supports
+ * both IPv4 and IPv6.
+ *
+ * From https://github.com/edazdarevic/CIDRUtils/blob/master/CIDRUtils.java
+ */
+@SuppressWarnings("all")
+public class CIDRUtils {
+ /**
+ * Cidr
+ */
+ private final String CIDR;
+ /**
+ * Inet address
+ */
+ private final InetAddress inetAddress;
+ /**
+ * Start address
+ */
+ private InetAddress startAddress;
+ /**
+ * End address
+ */
+ private InetAddress endAddress;
+ /**
+ * Prefix length
+ */
+ private final int prefixLength;
+
+
+ /**
+ * Cidr utils
+ *
+ * @param cidr cidr
+ * @throws UnknownHostException unknown host exception
+ * @since 1.5.0
+ */
+ public CIDRUtils(String cidr) throws UnknownHostException {
+
+ this.CIDR = cidr;
+
+ /* split CIDR to address and prefix part */
+ if (this.CIDR.contains("/")) {
+ int index = this.CIDR.indexOf("/");
+ String addressPart = this.CIDR.substring(0, index);
+ String networkPart = this.CIDR.substring(index + 1);
+
+ this.inetAddress = InetAddress.getByName(addressPart);
+ this.prefixLength = Integer.parseInt(networkPart);
+
+ this.calculate();
+ } else {
+ throw new IllegalArgumentException("not an valid CIDR format!");
+ }
+ }
+
+
+ /**
+ * Calculate
+ *
+ * @throws UnknownHostException unknown host exception
+ * @since 1.5.0
+ */
+ private void calculate() throws UnknownHostException {
+
+ ByteBuffer maskBuffer;
+ int targetSize;
+ if (this.inetAddress.getAddress().length == 4) {
+ maskBuffer =
+ ByteBuffer
+ .allocate(4)
+ .putInt(-1);
+ targetSize = 4;
+ } else {
+ maskBuffer = ByteBuffer.allocate(16)
+ .putLong(-1L)
+ .putLong(-1L);
+ targetSize = 16;
+ }
+
+ BigInteger mask = (new BigInteger(1, maskBuffer.array())).not().shiftRight(this.prefixLength);
+
+ ByteBuffer buffer = ByteBuffer.wrap(this.inetAddress.getAddress());
+ BigInteger ipVal = new BigInteger(1, buffer.array());
+
+ BigInteger startIp = ipVal.and(mask);
+ BigInteger endIp = startIp.add(mask.not());
+
+ byte[] startIpArr = this.toBytes(startIp.toByteArray(), targetSize);
+ byte[] endIpArr = this.toBytes(endIp.toByteArray(), targetSize);
+
+ this.startAddress = InetAddress.getByAddress(startIpArr);
+ this.endAddress = InetAddress.getByAddress(endIpArr);
+
+ }
+
+ /**
+ * To bytes
+ *
+ * @param array array
+ * @param targetSize target size
+ * @return the byte [ ]
+ * @since 1.5.0
+ */
+ private byte @NotNull [] toBytes(byte[] array, int targetSize) {
+ int counter = 0;
+ List newArr = new ArrayList();
+ while (counter < targetSize && (array.length - 1 - counter >= 0)) {
+ newArr.add(0, array[array.length - 1 - counter]);
+ counter++;
+ }
+
+ int size = newArr.size();
+ for (int i = 0; i < (targetSize - size); i++) {
+
+ newArr.add(0, (byte) 0);
+ }
+
+ byte[] ret = new byte[newArr.size()];
+ for (int i = 0; i < newArr.size(); i++) {
+ ret[i] = newArr.get(i);
+ }
+ return ret;
+ }
+
+ /**
+ * Gets network address *
+ *
+ * @return the network address
+ * @since 1.5.0
+ */
+ public String getNetworkAddress() {
+
+ return this.startAddress.getHostAddress();
+ }
+
+ /**
+ * Gets broadcast address *
+ *
+ * @return the broadcast address
+ * @since 1.5.0
+ */
+ public String getBroadcastAddress() {
+ return this.endAddress.getHostAddress();
+ }
+
+ /**
+ * Is in range
+ *
+ * @param ipAddress ip address
+ * @return the boolean
+ * @throws UnknownHostException unknown host exception
+ * @since 1.5.0
+ */
+ public boolean isInRange(String ipAddress) throws UnknownHostException {
+ InetAddress address = InetAddress.getByName(ipAddress);
+ BigInteger start = new BigInteger(1, this.startAddress.getAddress());
+ BigInteger end = new BigInteger(1, this.endAddress.getAddress());
+ BigInteger target = new BigInteger(1, address.getAddress());
+
+ int st = start.compareTo(target);
+ int te = target.compareTo(end);
+
+ return (st < 0 || st == 0) && (te < 0 || te == 0);
+ }
+}
diff --git a/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/util/DataTypeUtils.java b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/util/DataTypeUtils.java
new file mode 100644
index 0000000..8e475e2
--- /dev/null
+++ b/band/aivfo-element/aivfo-element-base/src/main/java/com/aivfo/el/starter/core/util/DataTypeUtils.java
@@ -0,0 +1,1685 @@
+package com.aivfo.el.starter.core.util;
+
+import com.aivfo.el.starter.base.exception.BasicException;
+import com.aivfo.el.starter.base.support.StrFormatter;
+import com.aivfo.el.starter.base.utils.JsonUtils;
+import lombok.experimental.UtilityClass;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.time.DateUtils;
+import org.jetbrains.annotations.Contract;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.lang.reflect.*;
+import java.math.BigDecimal;
+import java.text.SimpleDateFormat;
+import java.util.*;
+
+@Slf4j
+@UtilityClass
+@SuppressWarnings("all")
+public class DataTypeUtils {
+
+ /**
+ * 把输入数据转换为预期的输出类型
+ * 转换数据类型通常都是子类转换为父类或者转换为接口,这种情况至少占到程序编码90%以上,此方法内部默认此种判断为第一级判断,从而保证效率最优
+ * 此工具类的其他代码专注于解决另外的10%复杂情况,旨在把常见的容错代码,封装于此,避免调用端编写复杂、重复、冗长、不健壮的代码
+ * 容错概述:
+ * 1.自动拆箱装箱
+ * 2.数字与字符串向枚举转换
+ * 3.数组与Collection自动互转
+ * 4.除boolean外的七种基本数据类型互转时取值范围自适应
+ * 5.字符串与时间与基本数据类型互转
+ * 6.以及对上述容错情况自由排列组合后所产生的更复杂情况自动容错
+ * 7.请格外注意,由于Boolean在Java中的通俗约定较之其他语言例如C非常不同,不存在其他数据类型与之兼容取值范围的习惯做法,因此并未容错,如果想要让数字类型转换为boolean,
+ * 注意这里是未作支持的,现仅以最低兼容方式构造Boolean,即仅支持Java官方约定的通过字符串构造Boolean对象以及boolean基本值构造Boolean对象
+ * 常见情景:
+ * 1.比如一个int数组完全可以放入long数组或 List 中, 用此工具方法自动处理,即可避免自己的代码篇幅冗长,或者代码编写质量不高导致程序健壮性不高的问题
+ * 2.如果long数组数值并不是大数,当然也可以放入int数组,其他数值类型同理,反之亦然
+ * 3.很多数据拿到的时候是Object类型,如果用强制类型转换,很容易出错,比如到底Object是数组还是集合类,都有可能,这种时候强制类型转换必然出错
+ * 4.实现类自动转换为接口,比如HashMap、LinkedHashMap转换为Map
+ * 示例:
+ * log.info("{}", convert(byte[].class, "-128"));
+ * log.info("{}", convert(byte.class, "-128"));
+ * log.info("{}", convert(byte[].class, -128));
+ * log.info("{}", convert(byte.class, -128));
+ * log.info("{}", convert(Byte[].class, "-128"));
+ * log.info("{}", convert(Byte.class, "-128"));
+ * log.info("{}", convert(Byte[].class, -128));
+ * log.info("{}", convert(Byte.class, -128));
+ * log.info("{}", convert(byte[].class, "-128.00000"));
+ * log.info("{}", convert(byte.class, "-128.00000"));
+ * log.info("{}", convert(byte[].class, -128.00000));
+ * log.info("{}", convert(byte.class, -128.00000));
+ * log.info("{}", convert(Byte[].class, "-128.00000"));
+ * log.info("{}", convert(Byte.class, "-128.00000"));
+ * log.info("{}", convert(Byte[].class, -128.00000));
+ * log.info("{}", convert(Byte.class, -128.00000));
+ * log.info("{}", convert(float[].class, "-128.00000"));
+ * log.info("{}", convert(float.class, "-128.00000"));
+ * log.info("{}", convert(float[].class, -128.00000));
+ * log.info("{}", convert(float.class, -128.00000));
+ * log.info("{}", convert(Float[].class, "-128.00000"));
+ * log.info("{}", convert(Float.class, "-128.00000"));
+ * log.info("{}", convert(Float[].class, -128.00000));
+ * log.info("{}", convert(Float.class, -128.00000));
+ * log.info("{}", convert(BigDecimal.class, "-128"));
+ * log.info("{}", convert(BigDecimal.class, -128));
+ * log.info("{}", convert(BigDecimal.class, "-128.00000"));
+ * log.info("{}", convert(BigDecimal.class, -128.00000));
+ * log.info("{}", convert(Date.class, "0"));
+ * log.info("{}", convert(Date.class, "1451577600000"));
+ * log.info("{}", convert(Date[].class, new Object[]{"0","2016-01-01 00:00:00","1451577600000",1451577600000L}));
+ * log.info("{}", convert(Date[].class, Arrays.asList(new Object[]{"0","2016-01-01 00:00:00","1451577600000",1451577600000L})));
+ * log.info("{}", convert(List.class, new Date[]{new Date(0),new Date(1451577600000L)}));
+ * log.info("{}", convert(List.class, Arrays.asList(new Object[]{(byte)1,"1",1,1L})));
+ * log.info("{}", convert(Set.class, Arrays.asList(new Object[]{(byte)1,"1",1,1L})));
+ * log.info("{}", convert(Set.class, Arrays.asList(new Long[]{1L,1L,2L})));
+ * log.info("{}", convert(Set.class, new Long[]{1L,1L,2L}));
+ * log.info("{}", convert(BigDecimal[].class, new Object[]{(byte)1,1,1L,"1"}));
+ *
+ * @param the type parameter
+ * @param destClazz 预期将要输出为何种数据类型
+ * @param srcObj 输入任意类型的数据
+ * @return the t
+ * @since 1.0.0
+ */
+ @Contract("_, null -> null; null, !null -> null")
+ public static T convert(Class destClazz, Object srcObj) {
+ if (null == srcObj) {
+ return null;
+ }
+ if (null == destClazz) {
+ return null;
+ }
+ if (destClazz.isAssignableFrom(srcObj.getClass())) {
+ return (T) srcObj;
+ }
+
+ if (isPrimitive(destClazz)) {
+ Class> boxingType = typeBoxing(destClazz);
+ return (T) unBoxing(convert(boxingType, srcObj));
+ } else if (destClazz.isArray()) {
+ return convertArray(destClazz, srcObj);
+ } else if (destClazz.isEnum()) {
+ return Object2Enum.convert(destClazz, srcObj);
+ } else {
+ T ret = null;
+ if (String.class.isAssignableFrom(destClazz)) {
+ ret = (T) Object2String.convert(srcObj);
+ } else if (isNumber(destClazz)) {
+ if (Long.class.isAssignableFrom(destClazz)) {
+ ret = (T) Object2Long.convert(srcObj);
+ } else if (Integer.class.isAssignableFrom(destClazz)) {
+ ret = (T) Object2Integer.convert(srcObj);
+ } else if (BigDecimal.class.isAssignableFrom(destClazz)) {
+ ret = (T) Object2BigDecimal.convert(srcObj);
+ } else if (Double.class.isAssignableFrom(destClazz)) {
+ ret = (T) Object2Double.convert(srcObj);
+ } else if (Float.class.isAssignableFrom(destClazz)) {
+ ret = (T) Object2Float.convert(srcObj);
+ } else if (Short.class.isAssignableFrom(destClazz)) {
+ ret = (T) Object2Short.convert(srcObj);
+ } else if (Byte.class.isAssignableFrom(destClazz)) {
+ ret = (T) Object2Byte.convert(srcObj);
+ }
+ } else if (Date.class.isAssignableFrom(destClazz)) {
+ ret = (T) Object2Date.convert(srcObj);
+ } else if (Boolean.class.isAssignableFrom(destClazz)) {
+ ret = (T) Object2Boolean.convert(srcObj);
+ } else if (Character.class.isAssignableFrom(destClazz)) {
+ ret = (T) Object2Character.convert(srcObj);
+ } else if (Collection.class.isAssignableFrom(destClazz)) {
+ return (T) toCollection(destClazz, srcObj);
+ } else {
+ throw new BasicException("指定的数据类型 [{}] 暂不支持,无法转换", destClazz);
+ }
+ if (null == ret) {
+ throw new BasicException("[{}] 无法转换为 [{}] Data: [{}]",
+ srcObj.getClass(),
+ destClazz.getName(),
+ srcObj);
+ } else {
+ return ret;
+ }
+ }
+ }
+
+ /**
+ * To collection collection
+ *
+ * @param parameter
+ * @param destClazz dest clazz
+ * @param srcObj src obj
+ * @return the collection
+ * @since 1.0.0
+ */
+ @SuppressWarnings("unchecked")
+ private static Collection