diff --git a/README.md b/README.md index 50e79335fb..5070d181e2 100755 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

- + @@ -97,7 +97,7 @@ Bus (应用/服务总线) 是一个基础框架、服务套件,它基于Java17 org.aoju bus-all - 6.5.9 + 6.6.0 ``` diff --git a/bus-all/pom.xml b/bus-all/pom.xml index 6ef61c6166..8cb601d35c 100755 --- a/bus-all/pom.xml +++ b/bus-all/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-all - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-base/pom.xml b/bus-base/pom.xml index b2ff006745..6bff159a42 100755 --- a/bus-base/pom.xml +++ b/bus-base/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-base - 6.5.9 + 6.6.0 jar ${project.artifactId} @@ -42,7 +42,7 @@ UTF-8 UTF-8 17 - 2.7.3 + 2.7.5 1.18.24 2.2 diff --git a/bus-bom/pom.xml b/bus-bom/pom.xml index 74e4ddb936..d99f6737de 100755 --- a/bus-bom/pom.xml +++ b/bus-bom/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-bom - 6.5.9 + 6.6.0 pom ${project.artifactId} diff --git a/bus-cache/pom.xml b/bus-cache/pom.xml index 85d77dc852..8b7c0a42e5 100755 --- a/bus-cache/pom.xml +++ b/bus-cache/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-cache - 6.5.9 + 6.6.0 jar ${project.artifactId} @@ -42,7 +42,7 @@ UTF-8 UTF-8 17 - 2.7.3 + 2.7.5 1.18.24 5.1.0 4.2.3 diff --git a/bus-cache/src/main/java/org/aoju/bus/cache/metric/MemoryCache.java b/bus-cache/src/main/java/org/aoju/bus/cache/metric/MemoryCache.java index 9398d49d95..580455f38c 100755 --- a/bus-cache/src/main/java/org/aoju/bus/cache/metric/MemoryCache.java +++ b/bus-cache/src/main/java/org/aoju/bus/cache/metric/MemoryCache.java @@ -177,10 +177,10 @@ enum CacheScheduler { private ScheduledExecutorService scheduler; CacheScheduler() { - create(); + of(); } - private void create() { + private void of() { this.shutdown(); this.scheduler = new ScheduledThreadPoolExecutor(10, r -> new Thread(r, String.format("OAuth-Task-%s", cacheTaskNumber.getAndIncrement()))); } diff --git a/bus-core/README.md b/bus-core/README.md index 65d26c6ae3..75544d78ca 100755 --- a/bus-core/README.md +++ b/bus-core/README.md @@ -14,7 +14,7 @@ org.aoju bus-core - 6.5.9 + 6.6.0 ``` diff --git a/bus-core/pom.xml b/bus-core/pom.xml index f342d93a74..8934d89200 100755 --- a/bus-core/pom.xml +++ b/bus-core/pom.xml @@ -6,7 +6,7 @@ org.aoju bus-core - 6.5.9 + 6.6.0 jar ${project.artifactId} diff --git a/bus-core/src/main/java/org/aoju/bus/core/Version.java b/bus-core/src/main/java/org/aoju/bus/core/Version.java index c5d9b08e79..cee07c66cf 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/Version.java +++ b/bus-core/src/main/java/org/aoju/bus/core/Version.java @@ -60,7 +60,7 @@ public class Version { * @return 项目的版本号 */ public static String get() { - return "6.5.9.RELEASE"; + return "6.6.0.RELEASE"; } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/beans/BeanCache.java b/bus-core/src/main/java/org/aoju/bus/core/beans/BeanCache.java index db9a165e9d..0cf7ab9905 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/beans/BeanCache.java +++ b/bus-core/src/main/java/org/aoju/bus/core/beans/BeanCache.java @@ -29,7 +29,7 @@ import org.aoju.bus.core.map.WeakMap; /** - * Bean属性缓存 + * Bean缓存 * 缓存用于防止多次反射造成的性能问题 * * @author Kimi Liu diff --git a/bus-core/src/main/java/org/aoju/bus/core/beans/PathExpression.java b/bus-core/src/main/java/org/aoju/bus/core/beans/BeanPath.java similarity index 72% rename from bus-core/src/main/java/org/aoju/bus/core/beans/PathExpression.java rename to bus-core/src/main/java/org/aoju/bus/core/beans/BeanPath.java index 63815dac82..ca08675057 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/beans/PathExpression.java +++ b/bus-core/src/main/java/org/aoju/bus/core/beans/BeanPath.java @@ -43,7 +43,7 @@ * @author Kimi Liu * @since Java 17+ */ -public class PathExpression implements Serializable { +public class BeanPath implements Serializable { private static final long serialVersionUID = 1L; @@ -59,7 +59,7 @@ public class PathExpression implements Serializable { * * @param expression 表达式 */ - public PathExpression(final String expression) { + public BeanPath(final String expression) { init(expression); } @@ -83,10 +83,10 @@ public PathExpression(final String expression) { * * * @param expression 表达式 - * @return {@link PathExpression} + * @return {@link BeanPath} */ - public static PathExpression create(final String expression) { - return new PathExpression(expression); + public static BeanPath of(final String expression) { + return new BeanPath(expression); } private static Object getFieldValue(final Object bean, final String expression) { @@ -94,9 +94,9 @@ private static Object getFieldValue(final Object bean, final String expression) return null; } - if (StringKit.contains(expression, Symbol.C_COLON)) { + if (StringKit.contains(expression, ':')) { // [start:end:step] 模式 - final List parts = StringKit.splitTrim(expression, Symbol.C_COLON); + final List parts = StringKit.splitTrim(expression, ':'); final int start = Integer.parseInt(parts.get(0)); final int end = Integer.parseInt(parts.get(1)); int step = 1; @@ -108,8 +108,9 @@ private static Object getFieldValue(final Object bean, final String expression) } else if (ArrayKit.isArray(bean)) { return ArrayKit.sub(bean, start, end, step); } - } else if (StringKit.contains(expression, Symbol.C_COMMA)) { - final List keys = StringKit.splitTrim(expression, Symbol.C_COMMA); + } else if (StringKit.contains(expression, ',')) { + // [num0,num1,num2...]模式或者['key0','key1']模式 + final List keys = StringKit.splitTrim(expression, ','); if (bean instanceof Collection) { return CollKit.getAny((Collection) bean, Convert.convert(int[].class, keys)); } else if (ArrayKit.isArray(bean)) { @@ -117,14 +118,14 @@ private static Object getFieldValue(final Object bean, final String expression) } else { final String[] unWrappedKeys = new String[keys.size()]; for (int i = 0; i < unWrappedKeys.length; i++) { - unWrappedKeys[i] = StringKit.unWrap(keys.get(i), Symbol.C_SINGLE_QUOTE); + unWrappedKeys[i] = StringKit.unWrap(keys.get(i), '\''); } if (bean instanceof Map) { // 只支持String为key的Map - MapKit.getAny((Map) bean, unWrappedKeys); + return MapKit.getAny((Map) bean, unWrappedKeys); } else { final Map map = BeanKit.beanToMap(bean); - MapKit.getAny(map, unWrappedKeys); + return MapKit.getAny(map, unWrappedKeys); } } } else { @@ -135,19 +136,6 @@ private static Object getFieldValue(final Object bean, final String expression) return null; } - /** - * 对于非表达式去除单引号 - * - * @param expression 表达式 - * @return 表达式 - */ - private static String unWrapIfPossible(CharSequence expression) { - if (StringKit.containsAny(expression, " = ", " > ", " < ", " like ", Symbol.COMMA)) { - return expression.toString(); - } - return StringKit.unWrap(expression, Symbol.C_SINGLE_QUOTE); - } - /** * 获取表达式解析后的分段列表 * @@ -164,17 +152,53 @@ public List getPatternParts() { * @return 值, 如果对应值不存在, 则返回null */ public Object get(final Object bean) { - return get(this.patternParts, bean, false); + return get(this.patternParts, bean); } /** - * 判断path列表中末尾的标记是否为数字 + * 设置表达式指定位置(或filed对应)的值
+ * 若表达式指向一个List则设置其坐标对应位置的值,若指向Map则put对应key的值,Bean则设置字段的值
+ * 注意: * - * @param patternParts path列表 - * @return 是否为数字 + *

+     * 1. 如果为List,如果下标不大于List长度,则替换原有值,否则追加值
+     * 2. 如果为数组,如果下标不大于数组长度,则替换原有值,否则追加值
+     * 
+ * + * @param bean Bean、Map或List + * @param value 值 */ - private static boolean lastIsNumber(List patternParts) { - return MathKit.isInteger(patternParts.get(patternParts.size() - 1)); + public void set(final Object bean, final Object value) { + Objects.requireNonNull(bean); + + Object subBean = bean, previousBean; + boolean isFirst = true; + String patternPart; + // 尝试找到倒数第二个子对象, 最终需要设置它的字段值 + final int length = patternParts.size() - 1; + for (int i = 0; i < length; i++) { + patternPart = patternParts.get(i); + // 保存当前操作的bean, 以便subBean不存在时, 可以用来填充缺失的子对象 + previousBean = subBean; + // 获取当前对象的子对象 + subBean = getFieldValue(subBean, patternPart); + if (null == subBean) { + // 支持表达式的第一个对象为Bean本身(若用户定义表达式$开头,则不做此操作) + if (isFirst && false == this.isStartWith && BeanKit.isMatchName(bean, patternPart, true)) { + subBean = bean; + isFirst = false; + } else { + // 填充缺失的子对象, 根据下一个表达式决定填充的值, 如果是整数(下标)则使用列表, 否则当做Map对象 + subBean = MathKit.isInteger(patternParts.get(i + 1)) ? new ArrayList<>() : new HashMap<>(); + BeanKit.setFieldValue(previousBean, patternPart, subBean); + // 上面setFieldValue中有可能发生对象转换, 因此此处重新获取子对象 + // 欲知详情请自行阅读FieldUtil.setFieldValue(Object, Field, Object) + subBean = BeanKit.getFieldValue(previousBean, patternPart); + } + } + } + // 设置最终的字段值 + BeanKit.setFieldValue(subBean, patternParts.get(length), value); } @Override @@ -182,37 +206,20 @@ public String toString() { return this.patternParts.toString(); } - /** - * 获取父级路径列表 - * - * @param patternParts 路径列表 - * @return 父级路径列表 - */ - private static List getParentParts(List patternParts) { - return patternParts.subList(0, patternParts.size() - 1); - } - /** * 获取Bean中对应表达式的值 * - * @param patternParts 表达式分段列表 - * @param bean Bean对象或Map或List等 - * @param ignoreLast 是否忽略最后一个值,忽略最后一个值则用于set,否则用于read + * @param list 表达式分段列表 + * @param bean Bean对象或Map或List等 * @return 值, 如果对应值不存在, 则返回null */ - private Object get(final List patternParts, final Object bean, final boolean ignoreLast) { - int length = patternParts.size(); - if (ignoreLast) { - length--; - } + private Object get(final List list, final Object bean) { Object subBean = bean; boolean isFirst = true; - String patternPart; - for (int i = 0; i < length; i++) { - patternPart = patternParts.get(i); + for (String patternPart : list) { subBean = getFieldValue(subBean, patternPart); if (null == subBean) { - // 支持表达式的第一个对象为Bean本身(若用户定义表达式$开头,则不做此操作) + // 支持表达式的第一个对象为Bean本身(若用户定义表达式$开头,则不做此操作) if (isFirst && false == this.isStartWith && BeanKit.isMatchName(bean, patternPart, true)) { subBean = bean; isFirst = false; @@ -293,46 +300,4 @@ private void init(final String expression) { this.patternParts = CollKit.unmodifiable(localPatternParts); } - /** - * 设置表达式指定位置(或filed对应)的值 - * 若表达式指向一个List则设置其坐标对应位置的值,若指向Map则put对应key的值,Bean则设置字段的值 - * 注意: - * - *
-     * 1. 如果为List,如果下标不大于List长度,则替换原有值,否则追加值
-     * 2. 如果为数组,如果下标不大于数组长度,则替换原有值,否则追加值
-     * 
- * - * @param bean Bean、Map或List - * @param value 值 - */ - public void set(final Object bean, final Object value) { - set(bean, this.patternParts, lastIsNumber(this.patternParts), value); - } - - /** - * 设置表达式指定位置(或filed对应)的值 - * 若表达式指向一个List则设置其坐标对应位置的值,若指向Map则put对应key的值,Bean则设置字段的值 - * 注意: - * - *
-     * 1. 如果为List,如果下标不大于List长度,则替换原有值,否则追加值
-     * 2. 如果为数组,如果下标不大于数组长度,则替换原有值,否则追加值
-     * 
- * - * @param bean Bean、Map或List - * @param patternParts 表达式块列表 - * @param value 值 - */ - private void set(Object bean, List patternParts, boolean nextNumberPart, Object value) { - Object subBean = this.get(patternParts, bean, true); - if (null == subBean) { - final List parentParts = getParentParts(patternParts); - this.set(bean, parentParts, lastIsNumber(parentParts), nextNumberPart ? new ArrayList<>() : new HashMap<>()); - //set中有可能做过转换,因此此处重新获取bean - subBean = this.get(patternParts, bean, true); - } - BeanKit.setFieldValue(subBean, patternParts.get(patternParts.size() - 1), value); - } - } diff --git a/bus-core/src/main/java/org/aoju/bus/core/beans/PropertyCache.java b/bus-core/src/main/java/org/aoju/bus/core/beans/PropertyCache.java index 87991b8c3f..f1b4cee363 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/beans/PropertyCache.java +++ b/bus-core/src/main/java/org/aoju/bus/core/beans/PropertyCache.java @@ -33,7 +33,7 @@ import java.util.Map; /** - * Bean属性缓存 + * 属性缓存 * 缓存用于防止多次反射造成的性能问题 * * @author Kimi Liu diff --git a/bus-core/src/main/java/org/aoju/bus/core/beans/copier/provider/BeanValueProvider.java b/bus-core/src/main/java/org/aoju/bus/core/beans/copier/provider/BeanValueProvider.java index 1e7e69a9d1..86edd70d05 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/beans/copier/provider/BeanValueProvider.java +++ b/bus-core/src/main/java/org/aoju/bus/core/beans/copier/provider/BeanValueProvider.java @@ -45,78 +45,78 @@ */ public class BeanValueProvider implements ValueProvider { - final Map sourcePdMap; - private final Object source; - private final boolean ignoreError; + final Map sourcePdMap; + private final Object source; + private final boolean ignoreError; - /** - * 构造 - * - * @param bean Bean - * @param ignoreCase 是否忽略字段大小写 - * @param ignoreError 是否忽略字段值读取错误 - */ - public BeanValueProvider(Object bean, boolean ignoreCase, boolean ignoreError) { - this(bean, ignoreCase, ignoreError, null); - } + /** + * 构造 + * + * @param bean Bean + * @param ignoreCase 是否忽略字段大小写 + * @param ignoreError 是否忽略字段值读取错误 + */ + public BeanValueProvider(Object bean, boolean ignoreCase, boolean ignoreError) { + this(bean, ignoreCase, ignoreError, null); + } - /** - * 构造 - * - * @param bean Bean - * @param ignoreCase 是否忽略字段大小写 - * @param ignoreError 是否忽略字段值读取错误 - * @param keyEditor 键编辑器 - */ - public BeanValueProvider(Object bean, boolean ignoreCase, boolean ignoreError, Editor keyEditor) { - this.source = bean; - this.ignoreError = ignoreError; - final Map sourcePdMap = BeanKit.getBeanDesc(source.getClass()).getPropMap(ignoreCase); - // 如果用户定义了键编辑器,则提供的map中的数据必须全部转换key - this.sourcePdMap = new FuncKeyMap<>(new HashMap<>(sourcePdMap.size(), 1), (key) -> { - if (ignoreCase && key instanceof CharSequence) { - key = key.toString().toLowerCase(); - } - if (null != keyEditor) { - key = keyEditor.edit(key.toString()); - } - return key.toString(); - }); - this.sourcePdMap.putAll(sourcePdMap); - } + /** + * 构造 + * + * @param bean Bean + * @param ignoreCase 是否忽略字段大小写 + * @param ignoreError 是否忽略字段值读取错误 + * @param keyEditor 键编辑器 + */ + public BeanValueProvider(Object bean, boolean ignoreCase, boolean ignoreError, Editor keyEditor) { + this.source = bean; + this.ignoreError = ignoreError; + final Map sourcePdMap = BeanKit.getBeanDesc(source.getClass()).getPropMap(ignoreCase); + // 如果用户定义了键编辑器,则提供的map中的数据必须全部转换key + this.sourcePdMap = new FuncKeyMap<>(new HashMap<>(sourcePdMap.size(), 1), (key) -> { + if (ignoreCase && key instanceof CharSequence) { + key = key.toString().toLowerCase(); + } + if (null != keyEditor) { + key = keyEditor.edit(key.toString()); + } + return key.toString(); + }); + this.sourcePdMap.putAll(sourcePdMap); + } - @Override - public Object value(String key, Type valueType) { - final PropertyDesc sourcePd = getPropertyDesc(key, valueType); - Object result = null; - if (null != sourcePd) { - result = sourcePd.getValue(this.source, valueType, this.ignoreError); - } - return result; - } + @Override + public Object value(String key, Type valueType) { + final PropertyDesc sourcePd = getPropertyDesc(key, valueType); + Object result = null; + if (null != sourcePd) { + result = sourcePd.getValue(this.source, valueType, this.ignoreError); + } + return result; + } - @Override - public boolean containsKey(String key) { - final PropertyDesc sourcePd = getPropertyDesc(key, null); - // 字段描述不存在或忽略读的情况下,表示不存在 - return null != sourcePd && sourcePd.isReadable(false); - } + @Override + public boolean containsKey(String key) { + final PropertyDesc sourcePd = getPropertyDesc(key, null); + // 字段描述不存在或忽略读的情况下,表示不存在 + return null != sourcePd && sourcePd.isReadable(false); + } - /** - * 获得属性描述 - * - * @param key 字段名 - * @param valueType 值类型,用于判断是否为Boolean,可以为null - * @return 属性描述 - */ - private PropertyDesc getPropertyDesc(String key, Type valueType) { - PropertyDesc sourcePd = sourcePdMap.get(key); - if (null == sourcePd && (null == valueType || Boolean.class == valueType || boolean.class == valueType)) { - // boolean类型字段字段名支持两种方式 - sourcePd = sourcePdMap.get(StringKit.upperFirstAndAddPre(key, Normal.IS)); - } + /** + * 获得属性描述 + * + * @param key 字段名 + * @param valueType 值类型,用于判断是否为Boolean,可以为null + * @return 属性描述 + */ + private PropertyDesc getPropertyDesc(String key, Type valueType) { + PropertyDesc sourcePd = sourcePdMap.get(key); + if (null == sourcePd && (null == valueType || Boolean.class == valueType || boolean.class == valueType)) { + // boolean类型字段字段名支持两种方式 + sourcePd = sourcePdMap.get(StringKit.upperFirstAndAddPre(key, Normal.IS)); + } - return sourcePd; - } + return sourcePd; + } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/BitSetBloomFilter.java b/bus-core/src/main/java/org/aoju/bus/core/bloom/BitSetBloomFilter.java deleted file mode 100644 index d925c1a944..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/BitSetBloomFilter.java +++ /dev/null @@ -1,174 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.bloom; - -import org.aoju.bus.core.toolkit.FileKit; -import org.aoju.bus.core.toolkit.HashKit; -import org.aoju.bus.core.toolkit.IoKit; - -import java.io.BufferedReader; -import java.io.IOException; -import java.util.BitSet; - -/** - * BloomFilter实现方式2,此方式使用BitSet存储 - * Hash算法的使用使用固定顺序,只需指定个数即可 - * - * @author Kimi Liu - * @since Java 17+ - */ -public class BitSetBloomFilter implements BloomFilter { - - private static final long serialVersionUID = 1L; - - private final BitSet bitSet; - private final int bitSetSize; - private final int addedElements; - private final int hashFunctionNumber; - - /** - * 构造一个布隆过滤器,过滤器的容量为c * n 个bit - * - * @param c 当前过滤器预先开辟的最大包含记录,通常要比预计存入的记录多一倍 - * @param n 当前过滤器预计所要包含的记录 - * @param k 哈希函数的个数,等同每条记录要占用的bit数 - */ - public BitSetBloomFilter(int c, int n, int k) { - this.hashFunctionNumber = k; - this.bitSetSize = (int) Math.ceil(c * k); - this.addedElements = n; - this.bitSet = new BitSet(this.bitSetSize); - } - - /** - * 将字符串的字节表示进行多哈希编码 - * - * @param text 待添加进过滤器的字符串字节表示 - * @param hashNumber 要经过的哈希个数 - * @return 各个哈希的结果数组 - */ - public static int[] createHashes(String text, int hashNumber) { - int[] result = new int[hashNumber]; - for (int i = 0; i < hashNumber; i++) { - result[i] = hash(text, i); - - } - return result; - } - - /** - * 计算Hash值 - * - * @param text 被计算Hash的字符串 - * @param k Hash算法序号 - * @return Hash值 - */ - public static int hash(String text, int k) { - switch (k) { - case 0: - return HashKit.rsHash(text); - case 1: - return HashKit.jsHash(text); - case 2: - return HashKit.elfHash(text); - case 3: - return HashKit.bkdrHash(text); - case 4: - return HashKit.apHash(text); - case 5: - return HashKit.djbHash(text); - case 6: - return HashKit.sdbmHash(text); - case 7: - return HashKit.pjwHash(text); - default: - return 0; - } - } - - /** - * 通过文件初始化过滤器. - * - * @param path 文件路径 - * @param charset 字符集 - * @throws IOException IO异常 - */ - public void init(String path, String charset) throws IOException { - BufferedReader reader = FileKit.getReader(path, charset); - try { - String line; - while (true) { - line = reader.readLine(); - if (line == null) { - break; - } - this.add(line); - } - } finally { - IoKit.close(reader); - } - } - - @Override - public boolean add(String text) { - if (contains(text)) { - return false; - } - - int[] positions = createHashes(text, hashFunctionNumber); - for (int value : positions) { - int position = Math.abs(value % bitSetSize); - bitSet.set(position, true); - } - return true; - } - - /** - * 判定是否包含指定字符串 - * - * @param text 字符串 - * @return 是否包含,存在误差 - */ - @Override - public boolean contains(String text) { - int[] positions = createHashes(text, hashFunctionNumber); - for (int i : positions) { - int position = Math.abs(i % bitSetSize); - if (!bitSet.get(position)) { - return false; - } - } - return true; - } - - /** - * @return 得到当前过滤器的错误率 - */ - public double getFalsePositiveProbability() { - return Math.pow((1 - Math.exp(-hashFunctionNumber * (double) addedElements / bitSetSize)), hashFunctionNumber); - } - -} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/package-info.java b/bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/package-info.java deleted file mode 100644 index 8078bc02c5..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * BitMap实现 - * - * @author Kimi Liu - * @since Java 17+ - */ -package org.aoju.bus.core.bloom.bitmap; \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/AbstractFilter.java b/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/AbstractFilter.java deleted file mode 100644 index 91f14d939b..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/AbstractFilter.java +++ /dev/null @@ -1,115 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.bloom.filter; - -import org.aoju.bus.core.bloom.BloomFilter; -import org.aoju.bus.core.bloom.bitmap.BitMap; -import org.aoju.bus.core.bloom.bitmap.IntMap; -import org.aoju.bus.core.bloom.bitmap.LongMap; - -/** - * 抽象Bloom过滤器 - * - * @author Kimi Liu - * @since Java 17+ - */ -public abstract class AbstractFilter implements BloomFilter { - - private static final long serialVersionUID = 1L; - - /** - * 容量 - */ - protected long size = 0; - /** - * BitMap接口 - */ - private BitMap bm = null; - - /** - * 构造 - * - * @param maxValue 最大值 - * @param machineNum 机器位数 - */ - public AbstractFilter(long maxValue, int machineNum) { - init(maxValue, machineNum); - } - - /** - * 构造32位 - * - * @param maxValue 最大值 - */ - public AbstractFilter(long maxValue) { - this(maxValue, BitMap.MACHINE32); - } - - /** - * 初始化 - * - * @param maxValue 最大值 - * @param machineNum 机器位数 - */ - public void init(long maxValue, int machineNum) { - this.size = maxValue; - switch (machineNum) { - case BitMap.MACHINE32: - bm = new IntMap((int) (size / machineNum)); - break; - case BitMap.MACHINE64: - bm = new LongMap((int) (size / machineNum)); - break; - default: - throw new RuntimeException("Error Machine number!"); - } - } - - @Override - public boolean contains(String text) { - return bm.contains(Math.abs(hash(text))); - } - - @Override - public boolean add(String text) { - final long hash = Math.abs(hash(text)); - if (bm.contains(hash)) { - return false; - } - - bm.add(hash); - return true; - } - - /** - * 自定义Hash方法 - * - * @param text 字符串 - * @return the long - */ - public abstract long hash(String text); - -} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/PJWFilter.java b/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/PJWFilter.java deleted file mode 100644 index 4fbe21fe29..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/PJWFilter.java +++ /dev/null @@ -1,51 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.bloom.filter; - -import org.aoju.bus.core.toolkit.HashKit; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class PJWFilter extends AbstractFilter { - - private static final long serialVersionUID = 1L; - - public PJWFilter(long maxValue, int machineNum) { - super(maxValue, machineNum); - } - - public PJWFilter(long maxValue) { - super(maxValue); - } - - @Override - public long hash(String text) { - return HashKit.pjwHash(text) % size; - } - -} diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/SDBMFilter.java b/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/SDBMFilter.java deleted file mode 100644 index 0a90f6ef26..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/SDBMFilter.java +++ /dev/null @@ -1,51 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.bloom.filter; - -import org.aoju.bus.core.toolkit.HashKit; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class SDBMFilter extends AbstractFilter { - - private static final long serialVersionUID = 1L; - - public SDBMFilter(long maxValue, int machineNum) { - super(maxValue, machineNum); - } - - public SDBMFilter(long maxValue) { - super(maxValue); - } - - @Override - public long hash(String text) { - return HashKit.sdbmHash(text) % size; - } - -} diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/TianlFilter.java b/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/TianlFilter.java deleted file mode 100644 index 58499242b4..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/TianlFilter.java +++ /dev/null @@ -1,51 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.bloom.filter; - -import org.aoju.bus.core.toolkit.HashKit; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class TianlFilter extends AbstractFilter { - - private static final long serialVersionUID = 1L; - - public TianlFilter(long maxValue, int machineNum) { - super(maxValue, machineNum); - } - - public TianlFilter(long maxValue) { - super(maxValue); - } - - @Override - public long hash(String text) { - return HashKit.tianlHash(text) % size; - } - -} diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/package-info.java b/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/package-info.java deleted file mode 100644 index d9c6f561b3..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/package-info.java +++ /dev/null @@ -1,7 +0,0 @@ -/** - * 各种Hash算法的过滤器实现 - * - * @author Kimi Liu - * @since Java 17+ - */ -package org.aoju.bus.core.bloom.filter; \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/builder/ToStringBuilder.java b/bus-core/src/main/java/org/aoju/bus/core/builder/ToStringBuilder.java index ce964db992..4a440bdc03 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/builder/ToStringBuilder.java +++ b/bus-core/src/main/java/org/aoju/bus/core/builder/ToStringBuilder.java @@ -780,7 +780,7 @@ public ToStringBuilder append(final String fieldName, final Object object) { * value.

* * @param fieldName the field name - * @param object the value to add to the toString + * @param object the value to add to the toString * @param fullDetail true for detail, * false for summary info * @return this diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/BCD.java b/bus-core/src/main/java/org/aoju/bus/core/codec/BCD.java deleted file mode 100644 index e4818e0d9f..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/BCD.java +++ /dev/null @@ -1,155 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.codec; - -import org.aoju.bus.core.lang.Assert; -import org.aoju.bus.core.lang.Symbol; - -/** - * BCD码(Binary-Coded Decimal)亦称二进码十进数或二-十进制代码 - * BCD码这种编码形式利用了四个位元来储存一个十进制的数码, - * 使二进制和十进制之间的转换得以快捷的进行 - * - * @author Kimi Liu - * @since Java 17+ - */ -public class BCD { - - /** - * 字符串转BCD码 - * - * @param asc ASCII字符串 - * @return BCD - */ - public static byte[] strToBcd(String asc) { - Assert.notNull(asc, "ASCII must not be null!"); - int len = asc.length(); - int mod = len % 2; - if (mod != 0) { - asc = Symbol.ZERO + asc; - len = asc.length(); - } - if (len >= 2) { - len >>= 1; - } - byte[] bbt = new byte[len]; - byte[] abt = asc.getBytes(); - int j; - int k; - for (int p = 0; p < asc.length() / 2; p++) { - if ((abt[2 * p] >= Symbol.C_ZERO) && (abt[2 * p] <= Symbol.C_NINE)) { - j = abt[2 * p] - Symbol.C_ZERO; - } else if ((abt[2 * p] >= 'a') && (abt[2 * p] <= 'z')) { - j = abt[2 * p] - 'a' + 0x0a; - } else { - j = abt[2 * p] - 'A' + 0x0a; - } - if ((abt[2 * p + 1] >= Symbol.C_ZERO) && (abt[2 * p + 1] <= Symbol.C_NINE)) { - k = abt[2 * p + 1] - Symbol.C_ZERO; - } else if ((abt[2 * p + 1] >= 'a') && (abt[2 * p + 1] <= 'z')) { - k = abt[2 * p + 1] - 'a' + 0x0a; - } else { - k = abt[2 * p + 1] - 'A' + 0x0a; - } - int a = (j << 4) + k; - byte b = (byte) a; - bbt[p] = b; - } - return bbt; - } - - /** - * ASCII转BCD - * - * @param ascii ASCII byte数组 - * @return BCD - */ - public static byte[] ascToBcd(byte[] ascii) { - Assert.notNull(ascii, "Ascii must be not null!"); - return ascToBcd(ascii, ascii.length); - } - - /** - * ASCII转BCD - * - * @param ascii ASCII byte数组 - * @param ascLength 长度 - * @return BCD - */ - public static byte[] ascToBcd(byte[] ascii, int ascLength) { - Assert.notNull(ascii, "Ascii must be not null!"); - byte[] bcd = new byte[ascLength / 2]; - int j = 0; - for (int i = 0; i < (ascLength + 1) / 2; i++) { - bcd[i] = ascToBcd(ascii[j++]); - bcd[i] = (byte) (((j >= ascLength) ? 0x00 : ascToBcd(ascii[j++])) + (bcd[i] << 4)); - } - return bcd; - } - - /** - * BCD转ASCII字符串 - * - * @param bytes BCD byte数组 - * @return ASCII字符串 - */ - public static String bcdToString(byte[] bytes) { - Assert.notNull(bytes, "Bcd bytes must be not null!"); - char[] temp = new char[bytes.length * 2]; - char val; - - for (int i = 0; i < bytes.length; i++) { - val = (char) (((bytes[i] & 0xf0) >> 4) & 0x0f); - temp[i * 2] = (char) (val > 9 ? val + 'A' - 10 : val + Symbol.C_ZERO); - - val = (char) (bytes[i] & 0x0f); - temp[i * 2 + 1] = (char) (val > 9 ? val + 'A' - 10 : val + Symbol.C_ZERO); - } - return new String(temp); - } - - /** - * 转换单个byte为BCD - * - * @param asc ACSII - * @return BCD - */ - private static byte ascToBcd(byte asc) { - byte bcd; - - if ((asc >= Symbol.C_ZERO) && (asc <= Symbol.C_NINE)) { - bcd = (byte) (asc - Symbol.C_ZERO); - } else if ((asc >= 'A') && (asc <= 'F')) { - bcd = (byte) (asc - 'A' + 10); - } else if ((asc >= 'a') && (asc <= 'f')) { - bcd = (byte) (asc - 'a' + 10); - } else { - bcd = (byte) (asc - 48); - } - return bcd; - } - -} diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/Base32.java b/bus-core/src/main/java/org/aoju/bus/core/codec/Base32.java index e1fcf9b761..37c195ff28 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/Base32.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/Base32.java @@ -26,14 +26,18 @@ package org.aoju.bus.core.codec; import org.aoju.bus.core.codec.provider.Base32Provider; -import org.aoju.bus.core.lang.Charset; import org.aoju.bus.core.toolkit.StringKit; +import java.nio.charset.Charset; + /** - * Base32 - encodes and decodes RFC3548 Base32 (see http://www.faqs.org/rfcs/rfc3548.html ) - * base32就是用32(2的5次方)个特定ASCII码来表示256个ASCII码 - * 所以,5个ASCII字符经过base32编码后会变为8个字符(公约数为40),长度增加3/5.不足8n用“=”补足 - * see http://blog.csdn.net/earbao/article/details/44453937 + * Base32 - encodes and decodes RFC4648 Base32 (see https://datatracker.ietf.org/doc/html/rfc4648#section-6) + * base32就是用32(2的5次方)个特定ASCII码来表示256个ASCII码,所以5个ASCII字符经过base32编码后会变为8个字符(公约数为40) + * 长度增加3/5.不足8n用“=”补足,根据RFC4648 Base32规范,支持两种模式: + *
    + *
  • Base 32 Alphabet (ABCDEFGHIJKLMNOPQRSTUVWXYZ234567)
  • + *
  • "Extended Hex" Base 32 Alphabet (0123456789ABCDEFGHIJKLMNOPQRSTUV)
  • + *
* * @author Kimi Liu * @since Java 17+ @@ -56,8 +60,8 @@ public static String encode(final byte[] bytes) { * @param source 被编码的base32字符串 * @return 被加密后的字符串 */ - public static String encode(String source) { - return encode(source, Charset.UTF_8); + public static String encode(final String source) { + return encode(source, org.aoju.bus.core.lang.Charset.UTF_8); } /** @@ -67,7 +71,7 @@ public static String encode(String source) { * @param charset 字符集 * @return 被加密后的字符串 */ - public static String encode(String source, java.nio.charset.Charset charset) { + public static String encode(final String source, final Charset charset) { return encode(StringKit.bytes(source, charset)); } @@ -87,7 +91,7 @@ public static String encodeHex(final byte[] bytes) { * @param source 被编码的base32字符串 * @return 被加密后的字符串 */ - public static String encodeHex(String source) { + public static String encodeHex(final String source) { return encodeHex(source, org.aoju.bus.core.lang.Charset.UTF_8); } @@ -98,7 +102,7 @@ public static String encodeHex(String source) { * @param charset 字符集 * @return 被加密后的字符串 */ - public static String encodeHex(String source, java.nio.charset.Charset charset) { + public static String encodeHex(final String source, final Charset charset) { return encodeHex(StringKit.bytes(source, charset)); } @@ -108,7 +112,7 @@ public static String encodeHex(String source, java.nio.charset.Charset charset) * @param base32 base32编码 * @return 数据 */ - public static byte[] decode(String base32) { + public static byte[] decode(final String base32) { return Base32Provider.INSTANCE.decode(base32); } @@ -118,7 +122,7 @@ public static byte[] decode(String base32) { * @param source 被解码的base32字符串 * @return 被加密后的字符串 */ - public static String decodeString(String source) { + public static String decodeString(final String source) { return decodeString(source, org.aoju.bus.core.lang.Charset.UTF_8); } @@ -129,7 +133,7 @@ public static String decodeString(String source) { * @param charset 字符集 * @return 被加密后的字符串 */ - public static String decodeString(String source, java.nio.charset.Charset charset) { + public static String decodeString(final String source, final Charset charset) { return StringKit.toString(decode(source), charset); } @@ -139,7 +143,7 @@ public static String decodeString(String source, java.nio.charset.Charset charse * @param base32 base32编码 * @return 数据 */ - public static byte[] decodeHex(String base32) { + public static byte[] decodeHex(final String base32) { return Base32Provider.INSTANCE.decode(base32, true); } @@ -149,8 +153,8 @@ public static byte[] decodeHex(String base32) { * @param source 被解码的base32字符串 * @return 被加密后的字符串 */ - public static String decodeStrHex(String source) { - return decodeStrHex(source, org.aoju.bus.core.lang.Charset.UTF_8); + public static String decodeStringHex(final String source) { + return decodeStringHex(source, org.aoju.bus.core.lang.Charset.UTF_8); } /** @@ -160,7 +164,7 @@ public static String decodeStrHex(String source) { * @param charset 字符集 * @return 被加密后的字符串 */ - public static String decodeStrHex(String source, java.nio.charset.Charset charset) { + public static String decodeStringHex(final String source, final Charset charset) { return StringKit.toString(decodeHex(source), charset); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/Base58.java b/bus-core/src/main/java/org/aoju/bus/core/codec/Base58.java index de2cb3a86a..2a540a5f76 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/Base58.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/Base58.java @@ -26,7 +26,6 @@ package org.aoju.bus.core.codec; import org.aoju.bus.core.codec.provider.Base58Provider; -import org.aoju.bus.core.exception.InternalException; import org.aoju.bus.core.exception.ValidateException; import org.aoju.bus.core.lang.Algorithm; @@ -46,6 +45,16 @@ public class Base58 { private static final int CHECKSUM_SIZE = 4; + /** + * Base58编码 + * + * @param data 被编码的数据,不带校验和 + * @return 编码后的字符串 + */ + public static String encode(final byte[] data) { + return Base58Provider.INSTANCE.encode(data); + } + /** * Base58编码 * 包含版本位和校验位 @@ -54,18 +63,18 @@ public class Base58 { * @param data 被编码的数组,添加校验和。 * @return 编码后的字符串 */ - public static String encodeChecked(Integer version, byte[] data) { + public static String encodeChecked(final Integer version, final byte[] data) { return encode(addChecksum(version, data)); } /** - * Base58编码 + * Base58解码 * - * @param data 被编码的数据,不带校验和。 - * @return 编码后的字符串 + * @param encoded 被编码的base58字符串 + * @return 解码后的bytes */ - public static String encode(byte[] data) { - return Base58Provider.INSTANCE.encode(data); + public static byte[] decode(final CharSequence encoded) { + return Base58Provider.INSTANCE.decode(encoded); } /** @@ -76,10 +85,10 @@ public static String encode(byte[] data) { * @return 解码后的bytes * @throws ValidateException 标志位验证错误抛出此异常 */ - public static byte[] decodeChecked(CharSequence encoded) throws ValidateException { + public static byte[] decodeChecked(final CharSequence encoded) throws ValidateException { try { return decodeChecked(encoded, true); - } catch (ValidateException ignore) { + } catch (final ValidateException ignore) { return decodeChecked(encoded, false); } } @@ -93,21 +102,11 @@ public static byte[] decodeChecked(CharSequence encoded) throws ValidateExceptio * @return 解码后的bytes * @throws ValidateException 标志位验证错误抛出此异常 */ - public static byte[] decodeChecked(CharSequence encoded, boolean withVersion) throws ValidateException { - byte[] valueWithChecksum = decode(encoded); + public static byte[] decodeChecked(final CharSequence encoded, final boolean withVersion) throws ValidateException { + final byte[] valueWithChecksum = decode(encoded); return verifyAndRemoveChecksum(valueWithChecksum, withVersion); } - /** - * Base58解码 - * - * @param encoded 被编码的base58字符串 - * @return 解码后的bytes - */ - public static byte[] decode(CharSequence encoded) { - return Base58Provider.INSTANCE.decode(encoded); - } - /** * 验证并去除验证位和版本位 * @@ -115,7 +114,7 @@ public static byte[] decode(CharSequence encoded) { * @param withVersion 是否包含版本位 * @return 载荷数据 */ - private static byte[] verifyAndRemoveChecksum(byte[] data, boolean withVersion) { + private static byte[] verifyAndRemoveChecksum(final byte[] data, final boolean withVersion) { final byte[] payload = Arrays.copyOfRange(data, withVersion ? 1 : 0, data.length - CHECKSUM_SIZE); final byte[] checksum = Arrays.copyOfRange(data, data.length - CHECKSUM_SIZE, data.length); final byte[] expectedChecksum = checksum(payload); @@ -132,7 +131,7 @@ private static byte[] verifyAndRemoveChecksum(byte[] data, boolean withVersion) * @param payload Base58数据(不含校验码) * @return Base58数据 */ - private static byte[] addChecksum(Integer version, byte[] payload) { + private static byte[] addChecksum(final Integer version, final byte[] payload) { final byte[] addressBytes; if (null != version) { addressBytes = new byte[1 + payload.length + CHECKSUM_SIZE]; @@ -154,8 +153,8 @@ private static byte[] addChecksum(Integer version, byte[] payload) { * @param data 数据 * @return 校验码 */ - private static byte[] checksum(byte[] data) { - byte[] hash = hash256(hash256(data)); + private static byte[] checksum(final byte[] data) { + final byte[] hash = hash256(hash256(data)); return Arrays.copyOfRange(hash, 0, CHECKSUM_SIZE); } @@ -165,11 +164,11 @@ private static byte[] checksum(byte[] data) { * @param data 数据 * @return sha-256值 */ - private static byte[] hash256(byte[] data) { + private static byte[] hash256(final byte[] data) { try { return MessageDigest.getInstance(Algorithm.SHA256.getValue()).digest(data); - } catch (NoSuchAlgorithmException e) { - throw new InternalException(e); + } catch (final NoSuchAlgorithmException e) { + throw new ValidateException(e); } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/Base62.java b/bus-core/src/main/java/org/aoju/bus/core/codec/Base62.java index c21c2e3b0f..e8b2d0a9fb 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/Base62.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/Base62.java @@ -26,6 +26,7 @@ package org.aoju.bus.core.codec; import org.aoju.bus.core.codec.provider.Base62Provider; +import org.aoju.bus.core.lang.Charset; import org.aoju.bus.core.toolkit.FileKit; import org.aoju.bus.core.toolkit.IoKit; import org.aoju.bus.core.toolkit.StringKit; @@ -33,7 +34,6 @@ import java.io.File; import java.io.InputStream; import java.io.OutputStream; -import java.nio.charset.Charset; /** * Base62工具类,提供Base62的编码和解码方案 @@ -49,8 +49,8 @@ public class Base62 { * @param source 被编码的Base62字符串 * @return 被加密后的字符串 */ - public static String encode(CharSequence source) { - return encode(source, org.aoju.bus.core.lang.Charset.UTF_8); + public static String encode(final CharSequence source) { + return encode(source, Charset.UTF_8); } /** @@ -60,7 +60,7 @@ public static String encode(CharSequence source) { * @param charset 字符集 * @return 被加密后的字符串 */ - public static String encode(CharSequence source, Charset charset) { + public static String encode(final CharSequence source, final java.nio.charset.Charset charset) { return encode(StringKit.bytes(source, charset)); } @@ -70,7 +70,7 @@ public static String encode(CharSequence source, Charset charset) { * @param source 被编码的Base62字符串 * @return 被加密后的字符串 */ - public static String encode(byte[] source) { + public static String encode(final byte[] source) { return new String(Base62Provider.INSTANCE.encode(source)); } @@ -80,7 +80,7 @@ public static String encode(byte[] source) { * @param in 被编码Base62的流(一般为图片流或者文件流) * @return 被加密后的字符串 */ - public static String encode(InputStream in) { + public static String encode(final InputStream in) { return encode(IoKit.readBytes(in)); } @@ -90,7 +90,7 @@ public static String encode(InputStream in) { * @param file 被编码Base62的文件 * @return 被加密后的字符串 */ - public static String encode(File file) { + public static String encode(final File file) { return encode(FileKit.readBytes(file)); } @@ -100,8 +100,8 @@ public static String encode(File file) { * @param source 被编码的Base62字符串 * @return 被加密后的字符串 */ - public static String encodeInverted(CharSequence source) { - return encodeInverted(source, org.aoju.bus.core.lang.Charset.UTF_8); + public static String encodeInverted(final CharSequence source) { + return encodeInverted(source, Charset.UTF_8); } /** @@ -111,7 +111,7 @@ public static String encodeInverted(CharSequence source) { * @param charset 字符集 * @return 被加密后的字符串 */ - public static String encodeInverted(CharSequence source, Charset charset) { + public static String encodeInverted(final CharSequence source, final java.nio.charset.Charset charset) { return encodeInverted(StringKit.bytes(source, charset)); } @@ -121,7 +121,7 @@ public static String encodeInverted(CharSequence source, Charset charset) { * @param source 被编码的Base62字符串 * @return 被加密后的字符串 */ - public static String encodeInverted(byte[] source) { + public static String encodeInverted(final byte[] source) { return new String(Base62Provider.INSTANCE.encode(source, true)); } @@ -131,7 +131,7 @@ public static String encodeInverted(byte[] source) { * @param in 被编码Base62的流(一般为图片流或者文件流) * @return 被加密后的字符串 */ - public static String encodeInverted(InputStream in) { + public static String encodeInverted(final InputStream in) { return encodeInverted(IoKit.readBytes(in)); } @@ -141,18 +141,28 @@ public static String encodeInverted(InputStream in) { * @param file 被编码Base62的文件 * @return 被加密后的字符串 */ - public static String encodeInverted(File file) { + public static String encodeInverted(final File file) { return encodeInverted(FileKit.readBytes(file)); } /** * Base62解码 * - * @param source 被解码的Base62字符串 + * @param base62 被解码的Base62字符串 * @return 被加密后的字符串 */ - public static String decodeStrGbk(CharSequence source) { - return decodeString(source, org.aoju.bus.core.lang.Charset.GBK); + public static byte[] decode(final CharSequence base62) { + return decode(StringKit.bytes(base62, Charset.UTF_8)); + } + + /** + * 解码Base62 + * + * @param base62 Base62输入 + * @return 解码后的bytes + */ + public static byte[] decode(final byte[] base62) { + return Base62Provider.INSTANCE.decode(base62); } /** @@ -161,8 +171,8 @@ public static String decodeStrGbk(CharSequence source) { * @param source 被解码的Base62字符串 * @return 被加密后的字符串 */ - public static String decodeString(CharSequence source) { - return decodeString(source, org.aoju.bus.core.lang.Charset.UTF_8); + public static String decodeString(final CharSequence source) { + return decodeString(source, Charset.UTF_8); } /** @@ -172,50 +182,30 @@ public static String decodeString(CharSequence source) { * @param charset 字符集 * @return 被加密后的字符串 */ - public static String decodeString(CharSequence source, Charset charset) { + public static String decodeString(final CharSequence source, final java.nio.charset.Charset charset) { return StringKit.toString(decode(source), charset); } /** * Base62解码 * - * @param Base62 被解码的Base62字符串 + * @param base62 被解码的Base62字符串 * @param destFile 目标文件 * @return 目标文件 */ - public static File decodeToFile(CharSequence Base62, File destFile) { - return FileKit.writeBytes(decode(Base62), destFile); + public static File decodeToFile(final CharSequence base62, final File destFile) { + return FileKit.writeBytes(decode(base62), destFile); } /** * Base62解码 * - * @param base62Str 被解码的Base62字符串 + * @param base62 被解码的Base62字符串 * @param out 写出到的流 * @param isCloseOut 是否关闭输出流 */ - public static void decodeToStream(CharSequence base62Str, OutputStream out, boolean isCloseOut) { - IoKit.write(out, isCloseOut, decode(base62Str)); - } - - /** - * Base62解码 - * - * @param base62Str 被解码的Base62字符串 - * @return 被加密后的字符串 - */ - public static byte[] decode(CharSequence base62Str) { - return decode(StringKit.bytes(base62Str, org.aoju.bus.core.lang.Charset.UTF_8)); - } - - /** - * 解码Base62 - * - * @param base62bytes Base62输入 - * @return 解码后的bytes - */ - public static byte[] decode(byte[] base62bytes) { - return Base62Provider.INSTANCE.decode(base62bytes); + public static void decodeToStream(final CharSequence base62, final OutputStream out, final boolean isCloseOut) { + IoKit.write(out, isCloseOut, decode(base62)); } /** @@ -224,8 +214,8 @@ public static byte[] decode(byte[] base62bytes) { * @param source 被解码的Base62字符串 * @return 被加密后的字符串 */ - public static String decodeStrInverted(CharSequence source) { - return decodeStrInverted(source, org.aoju.bus.core.lang.Charset.UTF_8); + public static String decodeStrInverted(final CharSequence source) { + return decodeStrInverted(source, Charset.UTF_8); } /** @@ -235,7 +225,7 @@ public static String decodeStrInverted(CharSequence source) { * @param charset 字符集 * @return 被加密后的字符串 */ - public static String decodeStrInverted(CharSequence source, Charset charset) { + public static String decodeStrInverted(final CharSequence source, final java.nio.charset.Charset charset) { return StringKit.toString(decodeInverted(source), charset); } @@ -246,38 +236,39 @@ public static String decodeStrInverted(CharSequence source, Charset charset) { * @param destFile 目标文件 * @return 目标文件 */ - public static File decodeToFileInverted(CharSequence Base62, File destFile) { + public static File decodeToFileInverted(final CharSequence Base62, final File destFile) { return FileKit.writeBytes(decodeInverted(Base62), destFile); } /** * Base62解码(反转字母表模式) * - * @param base62Str 被解码的Base62字符串 + * @param base62 被解码的Base62字符串 * @param out 写出到的流 * @param isCloseOut 是否关闭输出流 */ - public static void decodeToStreamInverted(CharSequence base62Str, OutputStream out, boolean isCloseOut) { - IoKit.write(out, isCloseOut, decodeInverted(base62Str)); + public static void decodeToStreamInverted(final CharSequence base62, final OutputStream out, final boolean isCloseOut) { + IoKit.write(out, isCloseOut, decodeInverted(base62)); } /** * Base62解码(反转字母表模式) * - * @param base62Str 被解码的Base62字符串 + * @param base62 被解码的Base62字符串 * @return 被加密后的字符串 */ - public static byte[] decodeInverted(CharSequence base62Str) { - return decodeInverted(StringKit.bytes(base62Str, org.aoju.bus.core.lang.Charset.UTF_8)); + public static byte[] decodeInverted(final CharSequence base62) { + return decodeInverted(StringKit.bytes(base62, Charset.UTF_8)); } /** * 解码Base62(反转字母表模式) * - * @param base62bytes Base62输入 + * @param base62 Base62输入 * @return 解码后的bytes */ - public static byte[] decodeInverted(byte[] base62bytes) { - return Base62Provider.INSTANCE.decode(base62bytes, true); + public static byte[] decodeInverted(final byte[] base62) { + return Base62Provider.INSTANCE.decode(base62, true); } + } diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/Base64.java b/bus-core/src/main/java/org/aoju/bus/core/codec/Base64.java old mode 100644 new mode 100755 index ee2f3346f3..59f803c2b8 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/Base64.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/Base64.java @@ -38,9 +38,9 @@ import java.io.OutputStream; /** - * Base64工具类,提供Base64的编码和解码方案 - * base64编码是用64(2的6次方)个ASCII字符来表示256(2的8次方)个ASCII字符, - * 也就是三位二进制数组经过编码后变为四位的ASCII字符显示,长度比原来增加1/3 + * Base64工具类,提供Base64的编码和解码方案 + * base64编码是用64(2的6次方)个ASCII字符来表示256(2的8次方)个ASCII字符, + * 也就是三位二进制数组经过编码后变为四位的ASCII字符显示,长度比原来增加1/3 * * @author Kimi Liu * @since Java 17+ @@ -56,7 +56,7 @@ public class Base64 { * @param lineSep 在76个char之后是CRLF还是EOF * @return 编码后的bytes */ - public static byte[] encode(byte[] arr, boolean lineSep) { + public static byte[] encode(final byte[] arr, final boolean lineSep) { return lineSep ? java.util.Base64.getMimeEncoder().encode(arr) : java.util.Base64.getEncoder().encode(arr); @@ -68,8 +68,8 @@ public static byte[] encode(byte[] arr, boolean lineSep) { * @param source 被编码的base64字符串 * @return 被加密后的字符串 */ - public static String encode(CharSequence source) { - return encode(source, org.aoju.bus.core.lang.Charset.UTF_8); + public static String encode(final CharSequence source) { + return encode(source, Charset.UTF_8); } /** @@ -78,8 +78,8 @@ public static String encode(CharSequence source) { * @param source 被编码的base64字符串 * @return 被加密后的字符串 */ - public static String encodeUrlSafe(CharSequence source) { - return encodeUrlSafe(source, org.aoju.bus.core.lang.Charset.UTF_8); + public static String encodeUrlSafe(final CharSequence source) { + return encodeUrlSafe(source, Charset.UTF_8); } /** @@ -93,25 +93,14 @@ public static String encode(CharSequence source, String charset) { return encode(source, org.aoju.bus.core.lang.Charset.charset(charset)); } - /** - * base64编码,不进行padding(末尾不会填充'=') - * - * @param source 被编码的base64字符串 - * @param charset 编码 - * @return 被加密后的字符串 - */ - public static String encodeWithoutPadding(CharSequence source, String charset) { - return encodeWithoutPadding(StringKit.bytes(source, charset)); - } - /** * base64编码 * * @param source 被编码的base64字符串 * @param charset 字符集 - * @return 被加密后的字符串 + * @return 被编码后的字符串 */ - public static String encode(CharSequence source, java.nio.charset.Charset charset) { + public static String encode(final CharSequence source, final java.nio.charset.Charset charset) { return encode(StringKit.bytes(source, charset)); } @@ -122,7 +111,7 @@ public static String encode(CharSequence source, java.nio.charset.Charset charse * @param charset 字符集 * @return 被加密后的字符串 */ - public static String encodeUrlSafe(CharSequence source, java.nio.charset.Charset charset) { + public static String encodeUrlSafe(final CharSequence source, final java.nio.charset.Charset charset) { return encodeUrlSafe(StringKit.bytes(source, charset)); } @@ -132,7 +121,7 @@ public static String encodeUrlSafe(CharSequence source, java.nio.charset.Charset * @param source 被编码的base64字符串 * @return 被加密后的字符串 */ - public static String encode(byte[] source) { + public static String encode(final byte[] source) { return java.util.Base64.getEncoder().encodeToString(source); } @@ -142,7 +131,7 @@ public static String encode(byte[] source) { * @param source 被编码的base64字符串 * @return 被加密后的字符串 */ - public static String encodeWithoutPadding(byte[] source) { + public static String encodeWithoutPadding(final byte[] source) { return java.util.Base64.getEncoder().withoutPadding().encodeToString(source); } @@ -152,7 +141,7 @@ public static String encodeWithoutPadding(byte[] source) { * @param source 被编码的base64字符串 * @return 被加密后的字符串 */ - public static String encodeUrlSafe(byte[] source) { + public static String encodeUrlSafe(final byte[] source) { return java.util.Base64.getUrlEncoder().withoutPadding().encodeToString(source); } @@ -162,7 +151,7 @@ public static String encodeUrlSafe(byte[] source) { * @param in 被编码base64的流(一般为图片流或者文件流) * @return 被加密后的字符串 */ - public static String encode(InputStream in) { + public static String encode(final InputStream in) { return encode(IoKit.readBytes(in)); } @@ -172,7 +161,7 @@ public static String encode(InputStream in) { * @param in 被编码base64的流(一般为图片流或者文件流) * @return 被加密后的字符串 */ - public static String encodeUrlSafe(InputStream in) { + public static String encodeUrlSafe(final InputStream in) { return encodeUrlSafe(IoKit.readBytes(in)); } @@ -182,7 +171,7 @@ public static String encodeUrlSafe(InputStream in) { * @param file 被编码base64的文件 * @return 被加密后的字符串 */ - public static String encode(File file) { + public static String encode(final File file) { return encode(FileKit.readBytes(file)); } @@ -192,7 +181,7 @@ public static String encode(File file) { * @param file 被编码base64的文件 * @return 被加密后的字符串 */ - public static String encodeUrlSafe(File file) { + public static String encodeUrlSafe(final File file) { return encodeUrlSafe(FileKit.readBytes(file)); } @@ -321,19 +310,9 @@ public static void encode(byte[] src, int srcPos, int srcLen, char[] dest, * base64解码 * * @param source 被解码的base64字符串 - * @return 解码后的字符串 - */ - public static String decodeStrGbk(CharSequence source) { - return StringKit.toString(decode(source), Charset.GBK); - } - - /** - * base64解码 - * - * @param source 被解码的base64字符串 - * @return 解码后的字符串 + * @return 被加密后的字符串 */ - public static String decodeString(CharSequence source) { + public static String decodeString(final CharSequence source) { return decodeString(source, Charset.UTF_8); } @@ -355,7 +334,7 @@ public static String decodeString(CharSequence source, String charset) { * @param charset 字符集 * @return 解码后的字符串 */ - public static String decodeString(CharSequence source, java.nio.charset.Charset charset) { + public static String decodeString(final CharSequence source, final java.nio.charset.Charset charset) { return StringKit.toString(decode(source), charset); } @@ -366,7 +345,7 @@ public static String decodeString(CharSequence source, java.nio.charset.Charset * @param destFile 目标文件 * @return 目标文件 */ - public static File decodeToFile(CharSequence base64, File destFile) { + public static File decodeToFile(final CharSequence base64, final File destFile) { return FileKit.writeBytes(decode(base64), destFile); } @@ -377,7 +356,7 @@ public static File decodeToFile(CharSequence base64, File destFile) { * @param out 写出到的流 * @param isCloseOut 是否关闭输出流 */ - public static void decodeToStream(CharSequence base64, OutputStream out, boolean isCloseOut) { + public static void decodeToStream(final CharSequence base64, final OutputStream out, final boolean isCloseOut) { IoKit.write(out, isCloseOut, decode(base64)); } @@ -387,7 +366,7 @@ public static void decodeToStream(CharSequence base64, OutputStream out, boolean * @param base64 被解码的base64字符串 * @return 解码后的bytes */ - public static byte[] decode(CharSequence base64) { + public static byte[] decode(final CharSequence base64) { return decode(StringKit.bytes(base64, Charset.UTF_8)); } @@ -485,12 +464,12 @@ public static void decode(char[] ch, int off, int len, OutputStream out) { * @param base64 Base64的bytes * @return 是否为Base64 */ - public static boolean isBase64(CharSequence base64) { + public static boolean isBase64(final CharSequence base64) { if (base64 == null || base64.length() < 2) { return false; } - byte[] bytes = StringKit.bytes(base64, Charset.UTF_8); + final byte[] bytes = StringKit.bytes(base64); if (bytes.length != base64.length()) { // 如果长度不相等,说明存在双字节字符,肯定不是Base64,直接返回false @@ -506,12 +485,12 @@ public static boolean isBase64(CharSequence base64) { * @param base64Bytes Base64的bytes * @return 是否为Base64 */ - public static boolean isBase64(byte[] base64Bytes) { + public static boolean isBase64(final byte[] base64Bytes) { if (base64Bytes == null || base64Bytes.length < 3) { return false; } boolean hasPadding = false; - for (byte base64Byte : base64Bytes) { + for (final byte base64Byte : base64Bytes) { if (hasPadding) { if (Symbol.C_EQUAL != base64Byte) { // 前一个字符是'=',则后边的字符都必须是'=',即'='只能都位于结尾 @@ -527,16 +506,6 @@ public static boolean isBase64(byte[] base64Bytes) { return true; } - /** - * 给定的字符是否为Base64字符 - * - * @param octet 被检查的字符 - * @return 是否为Base64字符 - */ - public static boolean isBase64Code(byte octet) { - return octet == Symbol.C_EQUAL || (octet >= 0 && octet < Normal.DECODE_64_TABLE.length && Normal.DECODE_64_TABLE[octet] != -1); - } - /** * 获取下一个有效的byte字符 * @@ -561,6 +530,16 @@ private static byte getNextValidDecodeByte(byte[] in, MutableInt pos, int maxPos return PADDING; } + /** + * 给定的字符是否为Base64字符 + * + * @param octet 被检查的字符 + * @return 是否为Base64字符 + */ + public static boolean isBase64Code(byte octet) { + return octet == Symbol.C_EQUAL || (octet >= 0 && octet < Normal.DECODE_64_TABLE.length && Normal.DECODE_64_TABLE[octet] != -1); + } + private static boolean isWhiteSpace(byte byteToCheck) { switch (byteToCheck) { case Symbol.C_SPACE: diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/Caesar.java b/bus-core/src/main/java/org/aoju/bus/core/codec/Caesar.java index 5b28c89413..54dfc46307 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/Caesar.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/Caesar.java @@ -37,13 +37,13 @@ public class Caesar { /** - * 传入明文,加密得到密文 + * 传入明文,加密得到密文 * * @param message 加密的消息 * @param offset 偏移量 * @return 加密后的内容 */ - public static String encode(String message, int offset) { + public static String encode(final String message, final int offset) { Assert.notNull(message, "message must be not null!"); final int len = message.length(); final char[] plain = message.toCharArray(); @@ -61,17 +61,17 @@ public static String encode(String message, int offset) { /** * 传入明文解密到密文 * - * @param cipher 密文 - * @param offset 偏移量 + * @param cipherText 密文 + * @param offset 偏移量 * @return 解密后的内容 */ - public static String decode(String cipher, int offset) { - Assert.notNull(cipher, "cipherText must be not null!"); - final int len = cipher.length(); - final char[] plain = cipher.toCharArray(); + public static String decode(final String cipherText, final int offset) { + Assert.notNull(cipherText, "cipherText must be not null!"); + final int len = cipherText.length(); + final char[] plain = cipherText.toCharArray(); char c; for (int i = 0; i < len; i++) { - c = cipher.charAt(i); + c = cipherText.charAt(i); if (false == Character.isLetter(c)) { continue; } @@ -87,8 +87,8 @@ public static String decode(String cipher, int offset) { * @param offset 偏移量 * @return 加密后的字符 */ - private static char encodeChar(char c, int offset) { - int position = (Normal.UPPER_LOWER.indexOf(c) + offset) % 52; + private static char encodeChar(final char c, final int offset) { + final int position = (Normal.UPPER_LOWER.indexOf(c) + offset) % 52; return Normal.UPPER_LOWER.charAt(position); } @@ -100,7 +100,7 @@ private static char encodeChar(char c, int offset) { * @param offset 偏移量 * @return 解密后的字符 */ - private static char decodeChar(char c, int offset) { + private static char decodeChar(final char c, final int offset) { int position = (Normal.UPPER_LOWER.indexOf(c) - offset) % 52; if (position < 0) { position += 52; diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/Hashids.java b/bus-core/src/main/java/org/aoju/bus/core/codec/Hashids.java index 94550b0159..501942ff21 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/Hashids.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/Hashids.java @@ -25,6 +25,8 @@ ********************************************************************************/ package org.aoju.bus.core.codec; +import org.aoju.bus.core.lang.Normal; + import java.math.BigInteger; import java.util.*; import java.util.regex.Matcher; @@ -44,7 +46,7 @@ * * *

- * 来自:https://github.com/davidafsilva/java-hashids + * 来自:https://github.com/davidafsilva/java-hashids *

* *

@@ -57,38 +59,39 @@ */ public class Hashids implements Encoder, Decoder { - // 默认编解码字符串 - public static final char[] DEFAULT_ALPHABET = { - 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', - 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', - 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', - 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', - '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' - }; private static final int LOTTERY_MOD = 100; private static final double GUARD_THRESHOLD = 12; private static final double SEPARATOR_THRESHOLD = 3.5; - // 最小编解码字符串 + /** + * 最小编解码字符串 + */ private static final int MIN_ALPHABET_LENGTH = 16; private static final Pattern HEX_VALUES_PATTERN = Pattern.compile("[\\w\\W]{1,12}"); - // 默认分隔符 + /** + * 默认分隔符 + */ private static final char[] DEFAULT_SEPARATORS = { 'c', 'f', 'h', 'i', 's', 't', 'u', 'C', 'F', 'H', 'I', 'S', 'T', 'U' }; - - // algorithm properties + /** + * 算法属性 + */ private final char[] alphabet; - // 多个数字编解码的分界符 + /** + * 多个数字编解码的分界符 + */ private final char[] separators; private final Set separatorsSet; private final char[] salt; - // 补齐至 minLength 长度添加的字符列表 + /** + * 补齐至 minLength 长度添加的字符列表 + */ private final char[] guards; - // 编码后最小的字符长度 + /** + * 编码后最小的字符长度 + */ private final int minLength; - // region create - /** * 构造 * @@ -145,26 +148,25 @@ public Hashids(final char[] salt, final char[] alphabet, final int minLength) { } /** - * 根据参数值,创建{@code Hashids},使用默认{@link #DEFAULT_ALPHABET}作为字母表,不限制最小长度 + * 根据参数值,创建{@code Hashids},使用默认{@link Normal#LOWER_UPPER_NUMBER}作为字母表,不限制最小长度 * * @param salt 加盐值 * @return {@code Hashids} */ - public static Hashids create(final char[] salt) { - return create(salt, DEFAULT_ALPHABET, -1); + public static Hashids of(final char[] salt) { + return of(salt, Normal.LOWER_UPPER_NUMBER.toCharArray(), -1); } /** - * 根据参数值,创建{@code Hashids},使用默认{@link #DEFAULT_ALPHABET}作为字母表 + * 根据参数值,创建{@code Hashids},使用默认{@link Normal#LOWER_UPPER_NUMBER}作为字母表 * * @param salt 加盐值 * @param minLength 限制最小长度,-1表示不限制 * @return {@code Hashids} */ - public static Hashids create(final char[] salt, final int minLength) { - return create(salt, DEFAULT_ALPHABET, minLength); + public static Hashids of(final char[] salt, final int minLength) { + return of(salt, Normal.LOWER_UPPER_NUMBER.toCharArray(), minLength); } - // endregion /** * 根据参数值,创建{@code Hashids} @@ -174,7 +176,7 @@ public static Hashids create(final char[] salt, final int minLength) { * @param minLength 限制最小长度,-1表示不限制 * @return {@code Hashids} */ - public static Hashids create(final char[] salt, final char[] alphabet, final int minLength) { + public static Hashids of(final char[] salt, final char[] alphabet, final int minLength) { return new Hashids(salt, alphabet, minLength); } @@ -250,7 +252,7 @@ public String encode(final long... numbers) { // append the separator, if more numbers are pending encoding if (idx + 1 < numbers.length) { - long n = numbers[idx] % (global.charAt(initialLength) + 1); + final long n = numbers[idx] % (global.charAt(initialLength) + 1); global.append(separators[(int) (n % separators.length)]); } }); @@ -274,7 +276,7 @@ public String encode(final long... numbers) { final int initialSize = global.length(); if (paddingLeft > currentAlphabet.length) { // entire alphabet with the current encoding in the middle of it - int offset = alphabetHalfSize + (currentAlphabet.length % 2 == 0 ? 0 : 1); + final int offset = alphabetHalfSize + (currentAlphabet.length % 2 == 0 ? 0 : 1); global.insert(0, currentAlphabet, alphabetHalfSize, offset); global.insert(offset + initialSize, currentAlphabet, 0, alphabetHalfSize); @@ -297,10 +299,6 @@ public String encode(final long... numbers) { return global.toString(); } - //------------------------- - // Decode - //------------------------- - /** * 解码Hash值为16进制数字 * @@ -450,7 +448,7 @@ private char[] deriveNewAlphabet(final char[] alphabet, final char[] salt, final int offset = 1; // 2. salt if (salt.length > 0 && spaceLeft > 0) { - int length = Math.min(salt.length, spaceLeft); + final int length = Math.min(salt.length, spaceLeft); System.arraycopy(salt, 0, newSalt, offset, length); spaceLeft -= length; offset += length; @@ -492,7 +490,7 @@ private char[] validateAndFilterAlphabet(final char[] alphabet, final char[] sep // create a new alphabet without the duplicates final char[] uniqueAlphabet = new char[seen.size()]; int idx = 0; - for (char c : seen) { + for (final char c : seen) { uniqueAlphabet[idx++] = c; } return uniqueAlphabet; @@ -523,4 +521,5 @@ private char[] shuffle(final char[] alphabet, final char[] salt) { } return alphabet; } + } diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/Morse.java b/bus-core/src/main/java/org/aoju/bus/core/codec/Morse.java index 43a70bc271..d615f8bc18 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/Morse.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/Morse.java @@ -41,11 +41,17 @@ */ public class Morse { + /** + * code point -> morse + */ private static final Map ALPHABETS = new HashMap<>(); + /** + * morse -> code point + */ private static final Map DICTIONARIES = new HashMap<>(); static { - // Letters + // 字母 registerMorse('A', "01"); registerMorse('B', "1000"); registerMorse('C', "1010"); @@ -72,7 +78,7 @@ public class Morse { registerMorse('X', "1001"); registerMorse('Y', "1011"); registerMorse('Z', "1100"); - // Numbers + // 数字 registerMorse(Symbol.C_ZERO, "11111"); registerMorse(Symbol.C_ONE, "01111"); registerMorse(Symbol.C_TWO, "00111"); @@ -83,7 +89,7 @@ public class Morse { registerMorse(Symbol.C_SEVEN, "11000"); registerMorse(Symbol.C_EIGHT, "11100"); registerMorse(Symbol.C_NINE, "11110"); - // Punctuation + // 符号 registerMorse(Symbol.C_DOT, "010101"); registerMorse(Symbol.C_COMMA, "110011"); registerMorse(Symbol.C_QUESTION_MARK, "001100"); @@ -104,8 +110,17 @@ public class Morse { registerMorse(Symbol.C_AT, "011010"); } + /** + * 短标记或小点 + */ private final char dit; + /** + * 较长的标记或破折号 + */ private final char dah; + /** + * 分割符号 + */ private final char split; /** @@ -122,7 +137,7 @@ public Morse() { * @param dah 横线表示的字符 * @param split 分隔符 */ - public Morse(char dit, char dah, char split) { + public Morse(final char dit, final char dah, final char split) { this.dit = dit; this.dah = dah; this.split = split; @@ -152,7 +167,7 @@ public String encode(String text) { final StringBuilder morseBuilder = new StringBuilder(); final int len = text.codePointCount(0, text.length()); for (int i = 0; i < len; i++) { - int codePoint = text.codePointAt(i); + final int codePoint = text.codePointAt(i); String word = ALPHABETS.get(codePoint); if (null == word) { word = Integer.toBinaryString(codePoint); @@ -168,7 +183,7 @@ public String encode(String text) { * @param morse 莫尔斯电码 * @return 明文 */ - public String decode(String morse) { + public String decode(final String morse) { Assert.notNull(morse, "Morse should not be null."); final char dit = this.dit; diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/Percent.java b/bus-core/src/main/java/org/aoju/bus/core/codec/Percent.java index 85d7aa08b8..f85413d1b0 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/Percent.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/Percent.java @@ -25,9 +25,11 @@ ********************************************************************************/ package org.aoju.bus.core.codec; +import org.aoju.bus.core.codec.provider.Base16Provider; import org.aoju.bus.core.lang.Assert; import org.aoju.bus.core.lang.Normal; import org.aoju.bus.core.lang.Symbol; +import org.aoju.bus.core.toolkit.ArrayKit; import org.aoju.bus.core.toolkit.HexKit; import org.aoju.bus.core.toolkit.StringKit; @@ -35,6 +37,7 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.io.Serializable; +import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.BitSet; @@ -49,9 +52,10 @@ * @author Kimi Liu * @since Java 17+ */ -public class Percent implements Serializable { +public class Percent implements Encoder, Serializable { private static final long serialVersionUID = 1L; + /** * 存放安全编码 */ @@ -76,133 +80,71 @@ public Percent() { * * @param safeCharacters 安全字符,安全字符不被编码 */ - public Percent(BitSet safeCharacters) { + public Percent(final BitSet safeCharacters) { this.safeCharacters = safeCharacters; } /** - * 从已知Percent创建Percent,会复制给定Percent的安全字符 - * - * @param codec Percent - * @return this - */ - public static Percent of(Percent codec) { - return new Percent((BitSet) codec.safeCharacters.clone()); - } - - /** - * 创建Percent,使用指定字符串中的字符作为安全字符 - * - * @param chars 安全字符合集 - * @return this - */ - public static Percent of(CharSequence chars) { - Assert.notNull(chars, "chars must not be null"); - final Percent codec = new Percent(); - final int length = chars.length(); - for (int i = 0; i < length; i++) { - codec.addSafe(chars.charAt(i)); - } - return codec; - } - - /** - * 增加安全字符 - * 安全字符不被编码 - * - * @param c 字符 - * @return this - */ - public Percent addSafe(char c) { - safeCharacters.set(c); - return this; - } - - /** - * 移除安全字符 - * 安全字符不被编码 + * 检查给定字符是否为安全字符 * * @param c 字符 - * @return this - */ - public Percent removeSafe(char c) { - safeCharacters.clear(c); - return this; - } - - /** - * 增加安全字符到挡墙的Percent - * - * @param codec Percent - * @return this + * @return {@code true}表示安全,否则非安全字符 */ - public Percent or(Percent codec) { - this.safeCharacters.or(codec.safeCharacters); - return this; + public boolean isSafe(final char c) { + return this.safeCharacters.get(c); } - /** - * 组合当前Percent和指定Percent为一个新的Percent,安全字符为并集 - * - * @param codec Percent - * @return this - */ - public Percent orNew(Percent codec) { - return of(this).or(codec); - } + @Override + public byte[] encode(final byte[] bytes) { + // 初始容量计算,简单粗暴假设所有byte都需要转义,容量是三倍 + final ByteBuffer buffer = ByteBuffer.allocate(bytes.length * 3); + for (int i = 0; i < bytes.length; i++) { + encodeTo(buffer, bytes[i]); + } - /** - * 是否将空格编码为+ - * 如果为{@code true},则将空格编码为"+",此项只在"application/x-www-form-urlencoded"中使用 - * 如果为{@code false},则空格编码为"%20",此项一般用于URL的Query部分(RFC3986规范) - * - * @param encodeSpaceAsPlus 是否将空格编码为+ - * @return this - */ - public Percent setEncodeSpaceAsPlus(boolean encodeSpaceAsPlus) { - this.encodeSpaceAsPlus = encodeSpaceAsPlus; - return this; + return buffer.array(); } /** * 将URL中的字符串编码为%形式 * - * @param path 需要编码的字符串 - * @param charset 编码, {@code null}返回原字符串,表示不编码 + * @param path 需要编码的字符串 + * @param charset 编码, {@code null}返回原字符串,表示不编码 + * @param customSafeChar 自定义安全字符 * @return 编码后的字符串 */ - public String encode(CharSequence path, Charset charset) { + public String encode(final CharSequence path, final Charset charset, final char... customSafeChar) { if (null == charset || StringKit.isEmpty(path)) { return StringKit.toString(path); } - final StringBuilder rewrittenPath = new StringBuilder(path.length()); + final StringBuilder rewrittenPath = new StringBuilder(path.length() * 3); final ByteArrayOutputStream buf = new ByteArrayOutputStream(); final OutputStreamWriter writer = new OutputStreamWriter(buf, charset); - int c; + char c; for (int i = 0; i < path.length(); i++) { c = path.charAt(i); - if (safeCharacters.get(c)) { - rewrittenPath.append((char) c); + if (safeCharacters.get(c) || ArrayKit.contains(customSafeChar, c)) { + rewrittenPath.append(c); } else if (encodeSpaceAsPlus && c == Symbol.C_SPACE) { // 对于空格单独处理 - rewrittenPath.append('+'); + rewrittenPath.append(Symbol.C_PLUS); } else { // convert to external encoding before hex conversion try { - writer.write((char) c); + writer.write(c); writer.flush(); - } catch (IOException e) { + } catch (final IOException e) { buf.reset(); continue; } // 兼容双字节的Unicode符处理(如部分emoji) - byte[] ba = buf.toByteArray(); - for (byte toEncode : ba) { + final byte[] ba = buf.toByteArray(); + for (final byte toEncode : ba) { // Converting each byte in the buffer - rewrittenPath.append('%'); + rewrittenPath.append(Symbol.C_PERCENT); HexKit.appendHex(rewrittenPath, toEncode, false); } buf.reset(); @@ -211,4 +153,132 @@ public String encode(CharSequence path, Charset charset) { return rewrittenPath.toString(); } + /** + * 将单一byte转义到{@link ByteBuffer}中 + * + * @param buffer {@link ByteBuffer} + * @param b 字符byte + */ + private void encodeTo(final ByteBuffer buffer, final byte b) { + if (safeCharacters.get(b)) { + // 跳过安全字符 + buffer.put(b); + } else if (encodeSpaceAsPlus && b == Symbol.C_SPACE) { + // 对于空格单独处理 + buffer.put((byte) Symbol.C_PLUS); + } else { + buffer.put((byte) Symbol.C_PERCENT); + buffer.put((byte) Base16Provider.CODEC_UPPER.hexDigit(b >> 4)); + buffer.put((byte) Base16Provider.CODEC_UPPER.hexDigit(b)); + } + } + + /** + * {@link Percent}构建器 + * 由于{@link Percent}本身应该是只读对象,因此将此对象的构建放在Builder中 + */ + public static class Builder implements org.aoju.bus.core.builder.Builder { + + private static final long serialVersionUID = 1L; + private final Percent codec; + + private Builder(final Percent codec) { + this.codec = codec; + } + + /** + * 从已知Percent创建Percent,会复制给定Percent的安全字符 + * + * @param codec Percent + * @return this + */ + public static Builder of(final Percent codec) { + return new Builder(new Percent((BitSet) codec.safeCharacters.clone())); + } + + /** + * 创建Percent,使用指定字符串中的字符作为安全字符 + * + * @param chars 安全字符合集 + * @return this + */ + public static Builder of(final CharSequence chars) { + Assert.notNull(chars, "chars must not be null"); + final Builder builder = of(new Percent()); + final int length = chars.length(); + for (int i = 0; i < length; i++) { + builder.addSafe(chars.charAt(i)); + } + return builder; + } + + /** + * 增加安全字符 + * 安全字符不被编码 + * + * @param c 字符 + * @return this + */ + public Builder addSafe(final char c) { + codec.safeCharacters.set(c); + return this; + } + + /** + * 增加安全字符 + * 安全字符不被编码 + * + * @param chars 安全字符 + * @return this + */ + public Builder addSafes(final String chars) { + final int length = chars.length(); + for (int i = 0; i < length; i++) { + addSafe(chars.charAt(i)); + } + return this; + } + + /** + * 移除安全字符 + * 安全字符不被编码 + * + * @param c 字符 + * @return this + */ + public Builder removeSafe(final char c) { + codec.safeCharacters.clear(c); + return this; + } + + /** + * 增加安全字符到当前的Percent + * + * @param other {@link Percent} + * @return this + */ + public Builder or(final Percent other) { + codec.safeCharacters.or(other.safeCharacters); + return this; + } + + /** + * 是否将空格编码为+ + * 如果为{@code true},则将空格编码为"+",此项只在"application/x-www-form-urlencoded"中使用 + * 如果为{@code false},则空格编码为"%20",此项一般用于URL的Query部分(RFC3986规范) + * + * @param encodeSpaceAsPlus 是否将空格编码为+ + * @return this + */ + public Builder setEncodeSpaceAsPlus(final boolean encodeSpaceAsPlus) { + codec.encodeSpaceAsPlus = encodeSpaceAsPlus; + return this; + } + + @Override + public Percent build() { + return codec; + } + } + } diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/PunyCode.java b/bus-core/src/main/java/org/aoju/bus/core/codec/PunyCode.java index a369727170..fe795587ff 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/PunyCode.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/PunyCode.java @@ -30,6 +30,8 @@ import org.aoju.bus.core.lang.Symbol; import org.aoju.bus.core.toolkit.StringKit; +import java.util.List; + /** * Punycode是一个根据RFC 3492标准而制定的编码系统,主要用于把域名 * 从地方语言所采用的Unicode编码转换成为可用于DNS系统的编码 @@ -57,7 +59,7 @@ public class PunyCode { * @return PunyCode字符串 * @throws InternalException 计算异常 */ - public static String encode(CharSequence input) throws InternalException { + public static String encode(final CharSequence input) throws InternalException { return encode(input, false); } @@ -69,17 +71,17 @@ public static String encode(CharSequence input) throws InternalException { * @return PunyCode字符串 * @throws InternalException 计算异常 */ - public static String encode(CharSequence input, boolean withPrefix) throws InternalException { + public static String encode(final CharSequence input, final boolean withPrefix) throws InternalException { Assert.notNull(input, "input must not be null!"); int n = INITIAL_N; int delta = 0; int bias = INITIAL_BIAS; - StringBuilder output = new StringBuilder(); - // Copy all basic code points to the output final int length = input.length(); + final StringBuilder output = new StringBuilder(length * 4); + // Copy all basic code points to the output int b = 0; for (int i = 0; i < length; i++) { - char c = input.charAt(i); + final char c = input.charAt(i); if (isBasic(c)) { output.append(c); b++; @@ -109,7 +111,7 @@ public static String encode(CharSequence input, boolean withPrefix) throws Inter delta = delta + (m - n) * (h + 1); n = m; for (int j = 0; j < length; j++) { - int c = input.charAt(j); + final int c = input.charAt(j); if (c < n) { delta++; if (0 == delta) { @@ -119,7 +121,7 @@ public static String encode(CharSequence input, boolean withPrefix) throws Inter if (c == n) { int q = delta; for (int k = BASE; ; k += BASE) { - int t; + final int t; if (k <= bias) { t = TMIN; } else if (k >= bias + TMAX) { @@ -163,7 +165,8 @@ public static String decode(String input) throws InternalException { int n = INITIAL_N; int i = 0; int bias = INITIAL_BIAS; - StringBuilder output = new StringBuilder(); + final int length = input.length(); + final StringBuilder output = new StringBuilder(length / 4 + 1); int d = input.lastIndexOf(Symbol.C_MINUS); if (d > 0) { for (int j = 0; j < d; j++) { @@ -176,21 +179,20 @@ public static String decode(String input) throws InternalException { } else { d = 0; } - final int length = input.length(); while (d < length) { - int oldi = i; + final int oldi = i; int w = 1; for (int k = BASE; ; k += BASE) { if (d == length) { throw new InternalException("BAD_INPUT"); } - int c = input.charAt(d++); - int digit = codepoint2digit(c); + final int c = input.charAt(d++); + final int digit = codepoint2digit(c); if (digit > (Integer.MAX_VALUE - i) / w) { throw new InternalException("OVERFLOW"); } i = i + digit * w; - int t; + final int t; if (k <= bias) { t = TMIN; } else if (k >= bias + TMAX) { @@ -216,7 +218,49 @@ public static String decode(String input) throws InternalException { return output.toString(); } - private static int adapt(int delta, int numpoints, boolean first) { + /** + * 将域名编码为PunyCode,会忽略"."的编码 + * + * @param domain 域名 + * @return 编码后的域名 + * @throws InternalException 计算异常 + */ + public static String encodeDomain(final String domain) throws InternalException { + Assert.notNull(domain, "domain must not be null!"); + final List split = StringKit.split(domain, Symbol.C_DOT); + final StringBuilder result = new StringBuilder(domain.length() * 4); + for (final String str : split) { + if (result.length() != 0) { + result.append(Symbol.C_DOT); + } + result.append(encode(str, true)); + } + + return result.toString(); + } + + /** + * 解码 PunyCode为域名 + * + * @param domain 域名 + * @return 解码后的域名 + * @throws InternalException 计算异常 + */ + public static String decodeDomain(final String domain) throws InternalException { + Assert.notNull(domain, "domain must not be null!"); + final List split = StringKit.split(domain, Symbol.C_DOT); + final StringBuilder result = new StringBuilder(domain.length() / 4 + 1); + for (final String str : split) { + if (result.length() != 0) { + result.append(Symbol.C_DOT); + } + result.append(StringKit.startWithIgnoreEquals(str, PUNY_CODE_PREFIX) ? decode(str) : str); + } + + return result.toString(); + } + + private static int adapt(int delta, final int numpoints, final boolean first) { if (first) { delta = delta / DAMP; } else { @@ -231,7 +275,7 @@ private static int adapt(int delta, int numpoints, boolean first) { return k + ((BASE - TMIN + 1) * delta) / (delta + SKEW); } - private static boolean isBasic(char c) { + private static boolean isBasic(final char c) { return c < 0x80; } @@ -251,7 +295,7 @@ private static boolean isBasic(char c) { * @return 转换后的字符 * @throws InternalException 无效字符 */ - private static int digit2codepoint(int d) throws InternalException { + private static int digit2codepoint(final int d) throws InternalException { Assert.checkBetween(d, 0, 35); if (d < 26) { // 0..25 : 'a'..'z' @@ -280,7 +324,7 @@ private static int digit2codepoint(int d) throws InternalException { * @return 转换后的字符 * @throws InternalException 无效字符 */ - private static int codepoint2digit(int c) throws InternalException { + private static int codepoint2digit(final int c) throws InternalException { if (c - '0' < 10) { // '0'..'9' : 26..35 return c - '0' + 26; diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base16Provider.java b/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base16Provider.java index fdd0ab92a7..351c46d059 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base16Provider.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base16Provider.java @@ -28,6 +28,7 @@ import org.aoju.bus.core.codec.Decoder; import org.aoju.bus.core.codec.Encoder; import org.aoju.bus.core.exception.InternalException; +import org.aoju.bus.core.lang.Normal; import org.aoju.bus.core.toolkit.StringKit; /** @@ -41,8 +42,14 @@ public class Base16Provider implements Encoder, Decoder { /** - * 字符信息 + * 编码解码器:小写 */ + public static final Base16Provider CODEC_LOWER = new Base16Provider(true); + /** + * 编码解码器:大写 + */ + public static final Base16Provider CODEC_UPPER = new Base16Provider(false); + private final char[] alphabets; /** @@ -50,8 +57,8 @@ public class Base16Provider implements Encoder, Decoder>> 4];// 高位 - out[j++] = alphabets[0x0F & data[i]];// 低位 + out[j++] = hexDigit(data[i] >> 4);// 高位 + out[j++] = hexDigit(data[i]);// 低位 } return out; } @@ -122,12 +129,12 @@ public byte[] decode(CharSequence encoded) { * @param ch char值 * @return Unicode表现形式 */ - public String toUnicodeHex(char ch) { + public String toUnicodeHex(final char ch) { return "\\u" + - alphabets[(ch >> 12) & 15] + - alphabets[(ch >> 8) & 15] + - alphabets[(ch >> 4) & 15] + - alphabets[(ch) & 15]; + hexDigit(ch >> 12) + + hexDigit(ch >> 8) + + hexDigit(ch >> 4) + + hexDigit(ch); } /** @@ -136,11 +143,21 @@ public String toUnicodeHex(char ch) { * @param builder {@link StringBuilder} * @param b byte */ - public void appendHex(StringBuilder builder, byte b) { - int high = (b & 0xf0) >>> 4;// 高位 - int low = b & 0x0f;// 低位 - builder.append(alphabets[high]); - builder.append(alphabets[low]); + public void appendHex(final StringBuilder builder, final byte b) { + // 高位 + builder.append(hexDigit(b >> 4)); + // 低位 + builder.append(hexDigit(b)); + } + + /** + * 将byte值转为16进制 + * + * @param b byte + * @return the char + */ + public char hexDigit(final int b) { + return alphabets[b & 0x0f]; } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base32Provider.java b/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base32Provider.java index 912cda01ac..0d13748f5c 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base32Provider.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base32Provider.java @@ -52,7 +52,7 @@ public class Base32Provider implements Encoder, Decoder { * @param alphabet 自定义编码字母表,见 {@link #DEFAULT_ALPHABET}和 {@link #HEX_ALPHABET} * @param pad 补位字符 */ - public Base32Encoder(String alphabet, Character pad) { + public Base32Encoder(final String alphabet, final Character pad) { this.alphabet = alphabet.toCharArray(); this.pad = pad; } @Override - public String encode(byte[] data) { + public String encode(final byte[] data) { int i = 0; int index = 0; int digit; @@ -134,7 +134,7 @@ public String encode(byte[] data) { encodeLen = encodeLen + 1 + BASE32_FILL[(data.length * 8) % 5]; } - StringBuilder base32 = new StringBuilder(encodeLen); + final StringBuilder base32 = new StringBuilder(encodeLen); while (i < data.length) { // unsign @@ -201,7 +201,7 @@ public static class Base32Decoder implements Decoder { * * @param alphabet 编码字母表 */ - public Base32Decoder(String alphabet) { + public Base32Decoder(final String alphabet) { lookupTable = new byte[128]; Arrays.fill(lookupTable, (byte) -1); @@ -219,11 +219,11 @@ public Base32Decoder(String alphabet) { } @Override - public byte[] decode(CharSequence encoded) { + public byte[] decode(final CharSequence encoded) { int i, index, lookup, offset, digit; final String base32 = encoded.toString(); - int len = base32.endsWith("=") ? base32.indexOf("=") * 5 / 8 : base32.length() * 5 / 8; - byte[] bytes = new byte[len]; + final int len = base32.endsWith("=") ? base32.indexOf("=") * 5 / 8 : base32.length() * 5 / 8; + final byte[] bytes = new byte[len]; for (i = 0, index = 0, offset = 0; i < base32.length(); i++) { lookup = base32.charAt(i) - BASE_CHAR; diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base58Provider.java b/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base58Provider.java index 7bfd5cc062..aec4ee0fbb 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base58Provider.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base58Provider.java @@ -60,8 +60,8 @@ private static byte divmod(byte[] number, int firstDigit, int base, int divisor) // 用来表示输入数字的基数 int remainder = 0; for (int i = firstDigit; i < number.length; i++) { - int digit = (int) number[i] & 0xFF; - int temp = remainder * base + digit; + final int digit = (int) number[i] & 0xFF; + final int temp = remainder * base + digit; number[i] = (byte) (temp / divisor); remainder = temp % divisor; } @@ -75,7 +75,7 @@ private static byte divmod(byte[] number, int firstDigit, int base, int divisor) * @return 编码后的字符串 */ @Override - public String encode(byte[] data) { + public String encode(final byte[] data) { return Base58Encoder.ENCODER.encode(data); } @@ -87,7 +87,7 @@ public String encode(byte[] data) { * @throws IllegalArgumentException 非标准Base58字符串 */ @Override - public byte[] decode(CharSequence encoded) throws IllegalArgumentException { + public byte[] decode(final CharSequence encoded) throws IllegalArgumentException { return Base58Decoder.DECODER.decode(encoded); } @@ -95,11 +95,8 @@ public byte[] decode(CharSequence encoded) throws IllegalArgumentException { * Base58编码器 */ public static class Base58Encoder implements Encoder { + private static final String DEFAULT_ALPHABET = Normal.UPPER_NUMBER + "012345"; - /** - * 默认字符 - */ - private static final String DEFAULT_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; /** * 默认编码器 */ @@ -118,7 +115,7 @@ public static class Base58Encoder implements Encoder { * * @param alphabet 编码字母表 */ - public Base58Encoder(char[] alphabet) { + public Base58Encoder(final char[] alphabet) { this.alphabet = alphabet; alphabetZero = alphabet[0]; } @@ -177,7 +174,7 @@ public static class Base58Decoder implements Decoder { * * @param alphabet 编码字符表 */ - public Base58Decoder(String alphabet) { + public Base58Decoder(final String alphabet) { final byte[] lookupTable = new byte['z' + 1]; Arrays.fill(lookupTable, (byte) -1); @@ -189,15 +186,15 @@ public Base58Decoder(String alphabet) { } @Override - public byte[] decode(CharSequence encoded) { + public byte[] decode(final CharSequence encoded) { if (encoded.length() == 0) { return new byte[0]; } // Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits). final byte[] input58 = new byte[encoded.length()]; for (int i = 0; i < encoded.length(); ++i) { - char c = encoded.charAt(i); - int digit = c < 128 ? lookupTable[c] : -1; + final char c = encoded.charAt(i); + final int digit = c < 128 ? lookupTable[c] : -1; if (digit < 0) { throw new IllegalArgumentException(StringKit.format("Invalid char '{}' at [{}]", c, i)); } @@ -209,7 +206,7 @@ public byte[] decode(CharSequence encoded) { ++zeros; } // Convert base-58 digits to base-256 digits. - byte[] decoded = new byte[encoded.length()]; + final byte[] decoded = new byte[encoded.length()]; int outputStart = decoded.length; for (int inputStart = zeros; inputStart < input58.length; ) { decoded[--outputStart] = divmod(input58, inputStart, 58, 256); diff --git a/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base62Provider.java b/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base62Provider.java index cc902539e6..5fea76c3ee 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base62Provider.java +++ b/bus-core/src/main/java/org/aoju/bus/core/codec/provider/Base62Provider.java @@ -27,6 +27,7 @@ import org.aoju.bus.core.codec.Decoder; import org.aoju.bus.core.codec.Encoder; +import org.aoju.bus.core.lang.Normal; import org.aoju.bus.core.toolkit.ArrayKit; import java.io.ByteArrayOutputStream; @@ -58,7 +59,7 @@ public class Base62Provider implements Encoder, Decoder { - /** - * GMP风格 - */ - private static final byte[] GMP = { - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', - 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', - 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', - 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', - 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', - 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', - 'u', 'v', 'w', 'x', 'y', 'z' - }; - - /** - * 反转风格,即将GMP风格中的大小写做转换 - */ - private static final byte[] INVERTED = { - '0', '1', '2', '3', '4', '5', '6', '7', - '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', - 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', - 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', - 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', - 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', - 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', - 'U', 'V', 'W', 'X', 'Y', 'Z' - }; - /** * GMP 编码器 */ - public static Base62Encoder GMP_ENCODER = new Base62Encoder(GMP); + public static Base62Encoder GMP_ENCODER = new Base62Encoder(Normal.UPPER_LOWER_NUMBER.getBytes()); /** * INVERTED 编码器 */ - public static Base62Encoder INVERTED_ENCODER = new Base62Encoder(INVERTED); + public static Base62Encoder INVERTED_ENCODER = new Base62Encoder(Normal.LOWER_UPPER_NUMBER.getBytes()); /** * 字符信息 */ @@ -222,12 +195,12 @@ public static class Base62Encoder implements Encoder { * * @param alphabet 字符表 */ - public Base62Encoder(byte[] alphabet) { + public Base62Encoder(final byte[] alphabet) { this.alphabet = alphabet; } @Override - public byte[] encode(byte[] data) { + public byte[] encode(final byte[] data) { final byte[] indices = convert(data, STANDARD_BASE, TARGET_BASE); return translate(indices, alphabet); } @@ -241,11 +214,11 @@ public static class Base62Decoder implements Decoder { /** * GMP 解码器 */ - public static Base62Decoder GMP_DECODER = new Base62Decoder(Base62Encoder.GMP); + public static Base62Decoder GMP_DECODER = new Base62Decoder(Normal.UPPER_LOWER_NUMBER.getBytes()); /** * INVERTED 解码器 */ - public static Base62Decoder INVERTED_DECODER = new Base62Decoder(Base62Encoder.INVERTED); + public static Base62Decoder INVERTED_DECODER = new Base62Decoder(Normal.LOWER_UPPER_NUMBER.getBytes()); /** * 查找表 */ @@ -256,7 +229,7 @@ public static class Base62Decoder implements Decoder { * * @param alphabet 字母表 */ - public Base62Decoder(byte[] alphabet) { + public Base62Decoder(final byte[] alphabet) { lookupTable = new byte['z' + 1]; for (int i = 0; i < alphabet.length; i++) { lookupTable[alphabet[i]] = (byte) i; @@ -265,7 +238,7 @@ public Base62Decoder(byte[] alphabet) { @Override - public byte[] decode(byte[] encoded) { + public byte[] decode(final byte[] encoded) { final byte[] prepared = translate(encoded, lookupTable); return convert(prepared, TARGET_BASE, STANDARD_BASE); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/collection/UniqueKeySet.java b/bus-core/src/main/java/org/aoju/bus/core/collection/UniqueKeySet.java index d8cd55fdb3..65388c1828 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/collection/UniqueKeySet.java +++ b/bus-core/src/main/java/org/aoju/bus/core/collection/UniqueKeySet.java @@ -68,7 +68,7 @@ public UniqueKeySet(Function uniqueGenerator) { * @param uniqueGenerator 唯一键生成规则函数,用于生成对象对应的唯一键 */ public UniqueKeySet(boolean isLinked, Function uniqueGenerator) { - this(MapBuilder.create(isLinked), uniqueGenerator); + this(MapBuilder.of(isLinked), uniqueGenerator); } /** @@ -89,7 +89,7 @@ public UniqueKeySet(Function uniqueGenerator, Collection c) { * @param uniqueGenerator 唯一键生成规则函数,用于生成对象对应的唯一键 */ public UniqueKeySet(int initialCapacity, float loadFactor, Function uniqueGenerator) { - this(MapBuilder.create(new HashMap<>(initialCapacity, loadFactor)), uniqueGenerator); + this(MapBuilder.of(new HashMap<>(initialCapacity, loadFactor)), uniqueGenerator); } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/compiler/JavaSourceCompiler.java b/bus-core/src/main/java/org/aoju/bus/core/compiler/JavaSourceCompiler.java index a04f38e077..dee2ae599c 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/compiler/JavaSourceCompiler.java +++ b/bus-core/src/main/java/org/aoju/bus/core/compiler/JavaSourceCompiler.java @@ -27,8 +27,8 @@ * 使用方法如下: *

  *     ClassLoader classLoader = JavaSourceCompiler.create(null)
- *         .addSource(FileUtil.file("test-compile/b/B.java"))
- *         .addSource("c.C", FileUtil.readUtf8String("test-compile/c/C.java"))
+ *         .addSource(FileKit.file("test-compile/b/B.java"))
+ *         .addSource("c.C", FileKit.readUtf8String("test-compile/c/C.java"))
  *         // 增加编译依赖的类库
  *         .addLibrary(libFile)
  *         .compile();
diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/AbstractConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/AbstractConverter.java
index 905672a450..d30477dacf 100644
--- a/bus-core/src/main/java/org/aoju/bus/core/convert/AbstractConverter.java
+++ b/bus-core/src/main/java/org/aoju/bus/core/convert/AbstractConverter.java
@@ -103,7 +103,7 @@ protected String convertToString(final Object value) {
         } else if (ArrayKit.isArray(value)) {
             return ArrayKit.toString(value);
         } else if (CharsKit.isChar(value)) {
-            //对于ASCII字符使用缓存加速转换,减少空间创建
+            // 对于ASCII字符使用缓存加速转换,减少空间创建
             return CharsKit.toString((char) value);
         }
         return value.toString();
diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/ArrayConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/ArrayConverter.java
index e0a1c69bf1..c29cf890ad 100644
--- a/bus-core/src/main/java/org/aoju/bus/core/convert/ArrayConverter.java
+++ b/bus-core/src/main/java/org/aoju/bus/core/convert/ArrayConverter.java
@@ -43,12 +43,11 @@
  */
 public class ArrayConverter extends AbstractConverter {
 
-    private static final long serialVersionUID = 1L;
     /**
      * 单例对象
      */
     public static final ArrayConverter INSTANCE = new ArrayConverter();
-
+    private static final long serialVersionUID = 1L;
     /**
      * 是否忽略元素转换错误
      */
@@ -130,11 +129,11 @@ private Object convertObjectToArray(final Class targetComponentType, final Ob
 
             // 字符串转bytes,首先判断是否为Base64,是则转换,否则按照默认getBytes方法。
             if (targetComponentType == byte.class) {
-                final String str = value.toString();
-                if (Base64.isBase64(str)) {
+                final String text = value.toString();
+                if (Base64.isBase64(text)) {
                     return Base64.decode(value.toString());
                 }
-                return str.getBytes();
+                return text.getBytes();
             }
 
             // 单纯字符串情况下按照逗号分隔后劈开
diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/BasicType.java b/bus-core/src/main/java/org/aoju/bus/core/convert/BasicType.java
index 58d17eec4b..896c88aa69 100644
--- a/bus-core/src/main/java/org/aoju/bus/core/convert/BasicType.java
+++ b/bus-core/src/main/java/org/aoju/bus/core/convert/BasicType.java
@@ -36,6 +36,7 @@
  * @since Java 17+
  */
 public enum BasicType {
+
     /**
      * byte
      */
diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/BeanConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/BeanConverter.java
index ce9215f886..0cbd60a4e0 100644
--- a/bus-core/src/main/java/org/aoju/bus/core/convert/BeanConverter.java
+++ b/bus-core/src/main/java/org/aoju/bus/core/convert/BeanConverter.java
@@ -102,7 +102,7 @@ private Object convertInternal(final Type targetType, final Class targetClass
                 BeanKit.isBean(value.getClass())) {
             if (value instanceof Map && targetClass.isInterface()) {
                 // 将Map动态代理为Bean
-                return MapProxy.create((Map) value).toProxyBean(targetClass);
+                return MapProxy.of((Map) value).toProxyBean(targetClass);
             }
 
             // 限定被转换对象类型
diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/CharsetConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/CharsetConverter.java
index 01aeebd740..09c753f003 100644
--- a/bus-core/src/main/java/org/aoju/bus/core/convert/CharsetConverter.java
+++ b/bus-core/src/main/java/org/aoju/bus/core/convert/CharsetConverter.java
@@ -29,6 +29,9 @@
 
 /**
  * 编码对象转换器
+ *
+ * @author Kimi Liu
+ * @since Java 17+
  */
 public class CharsetConverter extends AbstractConverter {
 
diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/Convert.java b/bus-core/src/main/java/org/aoju/bus/core/convert/Convert.java
index 65e6d4183d..c5bba48770 100755
--- a/bus-core/src/main/java/org/aoju/bus/core/convert/Convert.java
+++ b/bus-core/src/main/java/org/aoju/bus/core/convert/Convert.java
@@ -799,7 +799,7 @@ public static String toSBC(final String input, final Set notConvertSe
         return new String(c);
     }
 
-	/**
+    /**
      * 全角转半角
      *
      * @param input String.
diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/EnumConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/EnumConverter.java
index c780560394..17b17c6395 100755
--- a/bus-core/src/main/java/org/aoju/bus/core/convert/EnumConverter.java
+++ b/bus-core/src/main/java/org/aoju/bus/core/convert/EnumConverter.java
@@ -46,7 +46,6 @@ public class EnumConverter extends AbstractConverter {
     private static final long serialVersionUID = 1L;
 
     public static final EnumConverter INSTANCE = new EnumConverter();
-
     private static final WeakMap, Map, Method>> VALUE_OF_METHOD_CACHE = new WeakMap<>();
 
     /**
@@ -95,13 +94,13 @@ protected static Enum tryConvertEnum(final Object value, final Class enumClass)
             //ignore
         }
 
-        //oriInt 应该滞后使用 以 GB/T 2261.1-2003 性别编码为例,对应整数并非连续数字会导致数字转枚举时失败
-        //0 - 未知的性别
-        //1 - 男性
-        //2 - 女性
-        //5 - 女性改(变)为男性
-        //6 - 男性改(变)为女性
-        //9 - 未说明的性别
+        // oriInt 应该滞后使用 以 GB/T 2261.1-2003 性别编码为例,对应整数并非连续数字会导致数字转枚举时失败
+        // 0 - 未知的性别
+        // 1 - 男性
+        // 2 - 女性
+        // 5 - 女性改(变)为男性
+        // 6 - 男性改(变)为女性
+        // 9 - 未说明的性别
         Enum enumResult = null;
         if (value instanceof Integer) {
             enumResult = EnumKit.getEnumAt(enumClass, (Integer) value);
@@ -142,7 +141,6 @@ protected Object convertInternal(final Class targetClass, final Object value)
         if (null != enumValue) {
             return enumValue;
         }
-
         throw new ConvertException("Can not convert {} to {}", value, targetClass);
     }
 
diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/LocaleConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/LocaleConverter.java
index babbd14765..269de5774a 100644
--- a/bus-core/src/main/java/org/aoju/bus/core/convert/LocaleConverter.java
+++ b/bus-core/src/main/java/org/aoju/bus/core/convert/LocaleConverter.java
@@ -25,6 +25,7 @@
  ********************************************************************************/
 package org.aoju.bus.core.convert;
 
+import org.aoju.bus.core.lang.Symbol;
 import org.aoju.bus.core.toolkit.StringKit;
 
 import java.util.Locale;
@@ -43,19 +44,19 @@ public class LocaleConverter extends AbstractConverter {
     @Override
     protected Locale convertInternal(final Class targetClass, final Object value) {
         try {
-            final String str = convertToString(value);
-            if (StringKit.isEmpty(str)) {
+            final String text = convertToString(value);
+            if (StringKit.isEmpty(text)) {
                 return null;
             }
 
-            final String[] items = str.split("_");
+            final String[] items = text.split(Symbol.UNDERLINE);
             if (items.length == 1) {
-                return new Locale(items[0]);
+                return Locale.of(items[0]);
             }
             if (items.length == 2) {
-                return new Locale(items[0], items[1]);
+                return Locale.of(items[0], items[1]);
             }
-            return new Locale(items[0], items[1], items[2]);
+            return Locale.of(items[0], items[1], items[2]);
         } catch (final Exception e) {
             // Ignore Exception
         }
diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/NumberConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/NumberConverter.java
index 5f02f21445..2c0ac2e293 100644
--- a/bus-core/src/main/java/org/aoju/bus/core/convert/NumberConverter.java
+++ b/bus-core/src/main/java/org/aoju/bus/core/convert/NumberConverter.java
@@ -197,29 +197,6 @@ protected static Number convert(final Object value, final Class targetClass, final Object value) {
-        return convert(value, (Class) targetClass, this::convertToString);
-    }
-
-    @Override
-    protected String convertToString(final Object value) {
-        final String result = StringKit.trim(super.convertToString(value));
-        if (null != result && result.length() > 1) {
-            // 非单个字符才判断末尾的标识符
-            final char c = Character.toUpperCase(result.charAt(result.length() - 1));
-            if (c == 'D' || c == 'L' || c == 'F') {
-                // 类型标识形式(例如123.6D)
-                return StringKit.subPre(result, -1);
-            }
-        }
-
-        if (StringKit.isEmpty(result)) {
-            throw new ConvertException("Can not convert empty value to Number!");
-        }
-        return result;
-    }
-
     /**
      * 转换为BigDecimal
      * 如果给定的值为空,或者转换失败,返回默认值
@@ -257,4 +234,27 @@ private static BigInteger toBigInteger(Object value, Function fu
         return MathKit.toBigInteger(func.apply(value));
     }
 
+    @Override
+    protected Number convertInternal(final Class targetClass, final Object value) {
+        return convert(value, (Class) targetClass, this::convertToString);
+    }
+
+    @Override
+    protected String convertToString(final Object value) {
+        final String result = StringKit.trim(super.convertToString(value));
+        if (null != result && result.length() > 1) {
+            // 非单个字符才判断末尾的标识符
+            final char c = Character.toUpperCase(result.charAt(result.length() - 1));
+            if (c == 'D' || c == 'L' || c == 'F') {
+                // 类型标识形式(例如123.6D)
+                return StringKit.subPre(result, -1);
+            }
+        }
+
+        if (StringKit.isEmpty(result)) {
+            throw new ConvertException("Can not convert empty value to Number!");
+        }
+        return result;
+    }
+
 }
diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/RegistryConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/RegistryConverter.java
index 14fcf63e27..989cea5871 100644
--- a/bus-core/src/main/java/org/aoju/bus/core/convert/RegistryConverter.java
+++ b/bus-core/src/main/java/org/aoju/bus/core/convert/RegistryConverter.java
@@ -202,7 +202,7 @@ private void register() {
         defaultConverterMap.put(SoftReference.class, ReferenceConverter.INSTANCE);
         defaultConverterMap.put(AtomicReference.class, new AtomicReferenceConverter());
 
-        //AtomicXXXArray,since 5.4.5
+        // AtomicXXXArray,since 5.4.5
         defaultConverterMap.put(AtomicIntegerArray.class, new AtomicIntegerArrayConverter());
         defaultConverterMap.put(AtomicLongArray.class, new AtomicLongArrayConverter());
 
diff --git a/bus-core/src/main/java/org/aoju/bus/core/convert/UUIDConverter.java b/bus-core/src/main/java/org/aoju/bus/core/convert/UUIDConverter.java
index fdca0f2ede..c9a904e59f 100644
--- a/bus-core/src/main/java/org/aoju/bus/core/convert/UUIDConverter.java
+++ b/bus-core/src/main/java/org/aoju/bus/core/convert/UUIDConverter.java
@@ -37,7 +37,6 @@ public class UUIDConverter extends AbstractConverter {
 
     private static final long serialVersionUID = 1L;
 
-
     @Override
     protected UUID convertInternal(final Class targetClass, final Object value) {
         return UUID.fromString(convertToString(value));
diff --git a/bus-core/src/main/java/org/aoju/bus/core/date/Almanac.java b/bus-core/src/main/java/org/aoju/bus/core/date/Almanac.java
index 11e3165df6..7fbae3f0f9 100644
--- a/bus-core/src/main/java/org/aoju/bus/core/date/Almanac.java
+++ b/bus-core/src/main/java/org/aoju/bus/core/date/Almanac.java
@@ -60,7 +60,7 @@ public class Almanac extends Converter {
      * @return int
      */
     public static int getYear(Date date) {
-        return Converter.toLocalDateTime(date).getYear();
+        return toLocalDateTime(date).getYear();
     }
 
     /**
@@ -70,7 +70,7 @@ public static int getYear(Date date) {
      * @return int
      */
     public static int getYear(Instant instant) {
-        return Converter.toLocalDateTime(instant).getYear();
+        return toLocalDateTime(instant).getYear();
     }
 
     /**
@@ -302,7 +302,7 @@ public static String getMonth(int month) {
      * @return int
      */
     public static int getMonth(Date date) {
-        return Converter.toLocalDateTime(date).getMonthValue();
+        return toLocalDateTime(date).getMonthValue();
     }
 
     /**
@@ -312,7 +312,7 @@ public static int getMonth(Date date) {
      * @return int
      */
     public static int getMonth(Instant instant) {
-        return Converter.toLocalDateTime(instant).getMonthValue();
+        return toLocalDateTime(instant).getMonthValue();
     }
 
     /**
@@ -819,7 +819,7 @@ public static int getDayOfMonth(int month, boolean isLeapYear) {
      * @return int
      */
     public static int getDayOfMonth(Date date) {
-        return Converter.toLocalDateTime(date).getDayOfMonth();
+        return toLocalDateTime(date).getDayOfMonth();
     }
 
     /**
@@ -829,7 +829,7 @@ public static int getDayOfMonth(Date date) {
      * @return int
      */
     public static int getDayOfMonth(Instant instant) {
-        return Converter.toLocalDateTime(instant).getDayOfMonth();
+        return toLocalDateTime(instant).getDayOfMonth();
     }
 
     /**
@@ -861,7 +861,7 @@ public static int getDayOfMonth(LocalDate localDate) {
      * @return int
      */
     public static int getDayOfYear(Date date) {
-        return Converter.toLocalDateTime(date).getDayOfYear();
+        return toLocalDateTime(date).getDayOfYear();
     }
 
     /**
@@ -871,7 +871,7 @@ public static int getDayOfYear(Date date) {
      * @return int
      */
     public static int getDayOfYear(Instant instant) {
-        return Converter.toLocalDateTime(instant).getDayOfYear();
+        return toLocalDateTime(instant).getDayOfYear();
     }
 
     /**
@@ -922,7 +922,7 @@ public static int getDayOfYear() {
      * @return int
      */
     public static int getHour(Date date) {
-        return Converter.toLocalDateTime(date).getHour();
+        return toLocalDateTime(date).getHour();
     }
 
     /**
@@ -932,7 +932,7 @@ public static int getHour(Date date) {
      * @return int
      */
     public static int getHour(Instant instant) {
-        return Converter.toLocalDateTime(instant).getHour();
+        return toLocalDateTime(instant).getHour();
     }
 
     /**
@@ -964,7 +964,7 @@ public static int getHour(LocalTime localTime) {
      * @return int
      */
     public static int getMinute(Date date) {
-        return Converter.toLocalDateTime(date).getMinute();
+        return toLocalDateTime(date).getMinute();
     }
 
     /**
@@ -974,7 +974,7 @@ public static int getMinute(Date date) {
      * @return int
      */
     public static int getMinute(Instant instant) {
-        return Converter.toLocalDateTime(instant).getMinute();
+        return toLocalDateTime(instant).getMinute();
     }
 
     /**
@@ -1006,7 +1006,7 @@ public static int getMinute(LocalTime localTime) {
      * @return int
      */
     public static int getSecond(Date date) {
-        return Converter.toLocalDateTime(date).getSecond();
+        return toLocalDateTime(date).getSecond();
     }
 
     /**
@@ -1016,7 +1016,7 @@ public static int getSecond(Date date) {
      * @return int
      */
     public static int getSecond(Instant instant) {
-        return Converter.toLocalDateTime(instant).getSecond();
+        return toLocalDateTime(instant).getSecond();
     }
 
     /**
@@ -1048,7 +1048,7 @@ public static int getSecond(LocalTime localTime) {
      * @return int
      */
     public static int getMillisecond(Date date) {
-        return Converter.toLocalDateTime(date).getNano() / 1_000_000;
+        return toLocalDateTime(date).getNano() / 1_000_000;
     }
 
     /**
@@ -1058,7 +1058,7 @@ public static int getMillisecond(Date date) {
      * @return int
      */
     public static int getMillisecond(Instant instant) {
-        return Converter.toLocalDateTime(instant).getNano() / 1_000_000;
+        return toLocalDateTime(instant).getNano() / 1_000_000;
     }
 
     /**
@@ -1115,7 +1115,7 @@ public static long getEpochSecond() {
      * @return String 格式: yyyy-MM-dd HH:mm:ss
      */
     public static String getEpochMilliFormat() {
-        return Formatter.format(new Date());
+        return format(new Date());
     }
 
     /**
@@ -1124,7 +1124,7 @@ public static String getEpochMilliFormat() {
      * @return String 格式: yyyy-MM-dd HH:mm:ss.SSS
      */
     public static String getEpochMilliFormatFull() {
-        return Formatter.format(new Date(), Fields.NORM_DATETIME_MS_PATTERN);
+        return format(new Date(), Fields.NORM_DATETIME_MS_PATTERN);
     }
 
     /**
@@ -1133,7 +1133,7 @@ public static String getEpochMilliFormatFull() {
      * @return String 格式: yyyy-MM-ddTHH:mm:ssZ
      */
     public static String getEpochMilliIsoNotFormatNoColon() {
-        return Formatter.format(new Date(), Fields.MSEC_PATTERN);
+        return format(new Date(), Fields.MSEC_PATTERN);
     }
 
     /**
@@ -1142,7 +1142,7 @@ public static String getEpochMilliIsoNotFormatNoColon() {
      * @return String 格式: yyyy-MM-dd'T'HH:mm:ss.SSSZ
      */
     public static String getEpochMilliIsoFormatFullNoColon() {
-        return Formatter.format(new Date(), Fields.MSEC_PATTERN);
+        return format(new Date(), Fields.MSEC_PATTERN);
     }
 
     /**
@@ -1154,7 +1154,7 @@ public static String getEpochMilliIsoFormatFullNoColon() {
      * @return Date
      */
     public static Date getDate(int year, int month, int dayOfMonth) {
-        return Converter.toDate(LocalDate.of(year, month, dayOfMonth));
+        return toDate(LocalDate.of(year, month, dayOfMonth));
     }
 
     /**
@@ -1169,7 +1169,7 @@ public static Date getDate(int year, int month, int dayOfMonth) {
      * @return Date
      */
     public static Date getDate(int year, int month, int dayOfMonth, int hour, int minute, int second) {
-        return Converter.toDate(LocalDateTime.of(year, month, dayOfMonth, hour, minute, second));
+        return toDate(LocalDateTime.of(year, month, dayOfMonth, hour, minute, second));
     }
 
     /**
@@ -1185,7 +1185,7 @@ public static Date getDate(int year, int month, int dayOfMonth, int hour, int mi
      * @return Date
      */
     public static Date getDate(int year, int month, int dayOfMonth, int hour, int minute, int second, int milliOfSecond) {
-        return Converter.toDate(LocalDateTime.of(year, month, dayOfMonth, hour, minute, second, milliOfSecond * 1000_000));
+        return toDate(LocalDateTime.of(year, month, dayOfMonth, hour, minute, second, milliOfSecond * 1000_000));
     }
 
     /**
@@ -1196,7 +1196,7 @@ public static Date getDate(int year, int month, int dayOfMonth, int hour, int mi
      * @return Date
      */
     public static Date getDateStartOfMonth(int year, int month) {
-        return Converter.toDateStartOfMonth(YearMonth.of(year, month));
+        return toDateStartOfMonth(YearMonth.of(year, month));
     }
 
     /**
@@ -1207,7 +1207,7 @@ public static Date getDateStartOfMonth(int year, int month) {
      * @return Date
      */
     public static Date getDateEndOfMonth(int year, int month) {
-        return Converter.toDateEndOfMonth(YearMonth.of(year, month));
+        return toDateEndOfMonth(YearMonth.of(year, month));
     }
 
     /**
@@ -1232,7 +1232,7 @@ public static int getAge(LocalDate birthDay) {
      * @return int 年龄
      */
     public static int getAge(Date birthDay) {
-        return getAge(Converter.toLocalDate(birthDay));
+        return getAge(toLocalDate(birthDay));
     }
 
     /**
@@ -1242,7 +1242,7 @@ public static int getAge(Date birthDay) {
      * @return int 年龄
      */
     public static int getAge(LocalDateTime birthDay) {
-        return getAge(Converter.toLocalDate(birthDay));
+        return getAge(toLocalDate(birthDay));
     }
 
     /**
@@ -1342,7 +1342,7 @@ public static String getAge(String birthDay, String dateToCompare) {
         Calendar birthday = new GregorianCalendar(Integer.valueOf(data[0]),
                 Integer.valueOf(data[1]), Integer.valueOf(data[2]));
         Calendar now = Calendar.getInstance();
-        now.setTime(Converter.parse(dateToCompare));
+        now.setTime(parse(dateToCompare));
 
         // 1.日相减
         int day = now.get(Calendar.DAY_OF_MONTH) - birthday.get(Calendar.DAY_OF_MONTH);
@@ -2030,8 +2030,8 @@ public static LocalDate withDayOfWeek(LocalDate localDate, long newValue) {
      * @return long
      */
     public static long betweenYears(LocalDateTime startInclusive, LocalDateTime endExclusive) {
-        return Period.between(Converter.toLocalDate(startInclusive),
-                Converter.toLocalDate(endExclusive)).getYears();
+        return Period.between(toLocalDate(startInclusive),
+                toLocalDate(endExclusive)).getYears();
     }
 
     /**
@@ -2043,8 +2043,8 @@ public static long betweenYears(LocalDateTime startInclusive, LocalDateTime endE
      * @return long
      */
     public static long betweenYears(Date startInclusive, Date endExclusive) {
-        return Period.between(Converter.toLocalDate(startInclusive),
-                Converter.toLocalDate(endExclusive)).getYears();
+        return Period.between(toLocalDate(startInclusive),
+                toLocalDate(endExclusive)).getYears();
     }
 
     /**
@@ -2068,8 +2068,8 @@ public static long betweenYears(LocalDate startInclusive, LocalDate endExclusive
      * @return long
      */
     public static long betweenMonths(LocalDateTime startInclusive, LocalDateTime endExclusive) {
-        return Period.between(Converter.toLocalDate(startInclusive),
-                Converter.toLocalDate(endExclusive)).getMonths();
+        return Period.between(toLocalDate(startInclusive),
+                toLocalDate(endExclusive)).getMonths();
     }
 
     /**
@@ -2081,8 +2081,8 @@ public static long betweenMonths(LocalDateTime startInclusive, LocalDateTime end
      * @return long
      */
     public static long betweenMonths(Date startInclusive, Date endExclusive) {
-        return Period.between(Converter.toLocalDate(startInclusive),
-                Converter.toLocalDate(endExclusive)).getMonths();
+        return Period.between(toLocalDate(startInclusive),
+                toLocalDate(endExclusive)).getMonths();
     }
 
     /**
@@ -2106,8 +2106,8 @@ public static long betweenMonths(LocalDate startInclusive, LocalDate endExclusiv
      * @return long
      */
     public static long betweenDays(LocalDateTime startInclusive, LocalDateTime endExclusive) {
-        return Period.between(Converter.toLocalDate(startInclusive),
-                Converter.toLocalDate(endExclusive)).getDays();
+        return Period.between(toLocalDate(startInclusive),
+                toLocalDate(endExclusive)).getDays();
     }
 
     /**
@@ -2119,8 +2119,8 @@ public static long betweenDays(LocalDateTime startInclusive, LocalDateTime endEx
      * @return long
      */
     public static long betweenDays(Date startInclusive, Date endExclusive) {
-        return Period.between(Converter.toLocalDate(startInclusive),
-                Converter.toLocalDate(endExclusive)).getDays();
+        return Period.between(toLocalDate(startInclusive),
+                toLocalDate(endExclusive)).getDays();
     }
 
     /**
@@ -2154,7 +2154,7 @@ public static long betweenTotalDays(LocalDateTime startInclusive, LocalDateTime
      * @return long
      */
     public static long betweenTotalDays(Date startInclusive, Date endExclusive) {
-        return durationBetween(Converter.toLocalDateTime(startInclusive), Converter.toLocalDateTime(endExclusive)).toDays();
+        return durationBetween(toLocalDateTime(startInclusive), toLocalDateTime(endExclusive)).toDays();
     }
 
     /**
@@ -2187,7 +2187,7 @@ public static long betweenTotalHours(LocalTime startInclusive, LocalTime endExcl
      * @return long
      */
     public static long betweenTotalHours(Date startInclusive, Date endExclusive) {
-        return durationBetween(Converter.toLocalDateTime(startInclusive), Converter.toLocalDateTime(endExclusive)).toHours();
+        return durationBetween(toLocalDateTime(startInclusive), toLocalDateTime(endExclusive)).toHours();
     }
 
     /**
@@ -2220,7 +2220,7 @@ public static long betweenTotalMinutes(LocalTime startInclusive, LocalTime endEx
      * @return long
      */
     public static long betweenTotalMinutes(Date startInclusive, Date endExclusive) {
-        return durationBetween(Converter.toLocalDateTime(startInclusive), Converter.toLocalDateTime(endExclusive)).toMinutes();
+        return durationBetween(toLocalDateTime(startInclusive), toLocalDateTime(endExclusive)).toMinutes();
     }
 
     /**
@@ -2253,7 +2253,7 @@ public static long betweenTotalSeconds(LocalTime startInclusive, LocalTime endEx
      * @return long
      */
     public static long betweenTotalSeconds(Date startInclusive, Date endExclusive) {
-        return durationBetween(Converter.toLocalDateTime(startInclusive), Converter.toLocalDateTime(endExclusive)).getSeconds();
+        return durationBetween(toLocalDateTime(startInclusive), toLocalDateTime(endExclusive)).getSeconds();
     }
 
     /**
@@ -2286,7 +2286,7 @@ public static long betweenTotalMillis(LocalTime startInclusive, LocalTime endExc
      * @return long
      */
     public static long betweenTotalMillis(Date startInclusive, Date endExclusive) {
-        return durationBetween(Converter.toLocalDateTime(startInclusive), Converter.toLocalDateTime(endExclusive)).toMillis();
+        return durationBetween(toLocalDateTime(startInclusive), toLocalDateTime(endExclusive)).toMillis();
     }
 
     /**
@@ -2319,7 +2319,7 @@ public static long betweenTotalNanos(LocalTime startInclusive, LocalTime endExcl
      * @return long
      */
     public static long betweenTotalNanos(Date startInclusive, Date endExclusive) {
-        return durationBetween(Converter.toLocalDateTime(startInclusive), Converter.toLocalDateTime(endExclusive)).toNanos();
+        return durationBetween(toLocalDateTime(startInclusive), toLocalDateTime(endExclusive)).toNanos();
     }
 
     /**
@@ -2329,7 +2329,7 @@ public static long betweenTotalNanos(Date startInclusive, Date endExclusive) {
      * @return int
      */
     public static int getDayOfWeek(Date date) {
-        return Converter.toLocalDateTime(date).getDayOfWeek().getValue();
+        return toLocalDateTime(date).getDayOfWeek().getValue();
     }
 
     /**
@@ -2359,7 +2359,7 @@ public static int getDayOfWeek(LocalDate localDate) {
      * @return int
      */
     public static int getDayOfWeek(Instant instant) {
-        return Converter.toLocalDateTime(instant).getDayOfWeek().getValue();
+        return toLocalDateTime(instant).getDayOfWeek().getValue();
     }
 
     /**
@@ -2589,7 +2589,7 @@ public static LocalDateTime firstDayOfMonth(LocalDateTime localDateTime) {
      * @return Date
      */
     public static Date firstDayOfMonth(Date date) {
-        return Converter.toDate(Converter.toLocalDateTime(date).with(TemporalAdjusters.firstDayOfMonth()));
+        return toDate(toLocalDateTime(date).with(TemporalAdjusters.firstDayOfMonth()));
     }
 
     /**
@@ -2619,7 +2619,7 @@ public static LocalDateTime lastDayOfMonth(LocalDateTime localDateTime) {
      * @return Date
      */
     public static Date lastDayOfMonth(Date date) {
-        return Converter.toDate(Converter.toLocalDateTime(date).with(TemporalAdjusters.lastDayOfMonth()));
+        return toDate(toLocalDateTime(date).with(TemporalAdjusters.lastDayOfMonth()));
     }
 
 
@@ -2999,7 +2999,7 @@ public static boolean isLeapYear(LocalDateTime localDateTime) {
      * @return boolean
      */
     public static boolean isLeapYear(Date date) {
-        return Converter.toLocalDateTime(date).toLocalDate().isLeapYear();
+        return toLocalDateTime(date).toLocalDate().isLeapYear();
     }
 
     /**
@@ -3055,7 +3055,7 @@ public static LocalDate nextLeapYear(LocalDate localDate) {
      * @return Date
      */
     public static Date nextLeapYear(Date date) {
-        return Converter.toDate(nextLeapYear(Converter.toLocalDateTime(date)));
+        return toDate(nextLeapYear(toLocalDateTime(date)));
     }
 
     /**
@@ -3148,7 +3148,7 @@ public static int lengthOfMonth(LocalDateTime localDateTime) {
      * @return int
      */
     public static int lengthOfMonth(Date date) {
-        return Converter.toLocalDateTime(date).toLocalDate().lengthOfMonth();
+        return toLocalDateTime(date).toLocalDate().lengthOfMonth();
     }
 
     /**
@@ -3178,7 +3178,7 @@ public static int lengthOfYear(LocalDateTime localDateTime) {
      * @return int
      */
     public static int lengthOfYear(Date date) {
-        return Converter.toLocalDateTime(date).toLocalDate().lengthOfYear();
+        return toLocalDateTime(date).toLocalDate().lengthOfYear();
     }
 
     /**
@@ -3211,7 +3211,7 @@ public static LocalDateTime next(LocalDateTime localDateTime, DayOfWeek dayOfWee
      * @return Date
      */
     public static Date next(Date date, DayOfWeek dayOfWeek) {
-        return Converter.toDate(Converter.toLocalDateTime(date).with(TemporalAdjusters.next(dayOfWeek)));
+        return toDate(toLocalDateTime(date).with(TemporalAdjusters.next(dayOfWeek)));
     }
 
 
@@ -3245,7 +3245,7 @@ public static LocalDateTime previous(LocalDateTime localDateTime, DayOfWeek dayO
      * @return Date
      */
     public static Date previous(Date date, DayOfWeek dayOfWeek) {
-        return Converter.toDate(Converter.toLocalDateTime(date).with(TemporalAdjusters.previous(dayOfWeek)));
+        return toDate(toLocalDateTime(date).with(TemporalAdjusters.previous(dayOfWeek)));
     }
 
     /**
@@ -3275,7 +3275,7 @@ public static LocalDateTime nextWorkDay(LocalDateTime localDateTime) {
      * @return Date
      */
     public static Date nextWorkDay(Date date) {
-        return Converter.toDate(Converter.toLocalDateTime(date).with(nextWorkDay()));
+        return toDate(toLocalDateTime(date).with(nextWorkDay()));
     }
 
     /**
@@ -3363,7 +3363,7 @@ public static Temporal with(Temporal temporal, TemporalField field, long newValu
      * @return Date
      */
     public static Date with(Date date, TemporalField field, long newValue) {
-        return Converter.toDate(Converter.toLocalDateTime(date).with(field, newValue));
+        return toDate(toLocalDateTime(date).with(field, newValue));
     }
 
     /**
@@ -3454,7 +3454,7 @@ public static String transform(Date date, String zoneId) {
      * @return 日期 yyyy-MM-dd HH:mm:ss
      */
     public static String transform(Date date, java.time.ZoneId zone) {
-        return Formatter.format(date, zone.toString());
+        return format(date, zone.toString());
     }
 
     /**
@@ -3465,7 +3465,7 @@ public static String transform(Date date, java.time.ZoneId zone) {
      * @return int date1 大于 date2 返回1, date1 小于 date2 返回-1,date1 等于date2 返回0
      */
     public static int compare(Date date1, Date date2) {
-        return compare(Converter.toLocalDateTime(date1), Converter.toLocalDateTime(date2));
+        return compare(toLocalDateTime(date1), toLocalDateTime(date2));
     }
 
     /**
@@ -3562,7 +3562,7 @@ public static LocalTime endAccuracyTimeOfDay() {
      * @return Date
      */
     public static Date startTimeOfYesterday() {
-        return Converter.toDate(LocalDate.now().minusDays(1).atTime(startTimeOfDay()));
+        return toDate(LocalDate.now().minusDays(1).atTime(startTimeOfDay()));
     }
 
     /**
@@ -3571,7 +3571,7 @@ public static Date startTimeOfYesterday() {
      * @return Date
      */
     public static Date endTimeOfYesterday() {
-        return Converter.toDate(LocalDate.now().minusDays(1).atTime(endTimeOfDay()));
+        return toDate(LocalDate.now().minusDays(1).atTime(endTimeOfDay()));
     }
 
     /**
@@ -3580,7 +3580,7 @@ public static Date endTimeOfYesterday() {
      * @return Date
      */
     public static Date startTimeOfTomorrow() {
-        return Converter.toDate(LocalDate.now().plusDays(1).atTime(startTimeOfDay()));
+        return toDate(LocalDate.now().plusDays(1).atTime(startTimeOfDay()));
     }
 
     /**
@@ -3589,7 +3589,7 @@ public static Date startTimeOfTomorrow() {
      * @return Date
      */
     public static Date endTimeOfTomorrow() {
-        return Converter.toDate(LocalDate.now().plusDays(1).atTime(endTimeOfDay()));
+        return toDate(LocalDate.now().plusDays(1).atTime(endTimeOfDay()));
     }
 
     /**
@@ -3598,7 +3598,7 @@ public static Date endTimeOfTomorrow() {
      * @return Date
      */
     public static Date startTimeOfToday() {
-        return Converter.toDate(LocalDate.now().atTime(startTimeOfDay()));
+        return toDate(LocalDate.now().atTime(startTimeOfDay()));
     }
 
     /**
@@ -3607,7 +3607,7 @@ public static Date startTimeOfToday() {
      * @return Date
      */
     public static Date endTimeOfToday() {
-        return Converter.toDate(LocalDate.now().atTime(endTimeOfDay()));
+        return toDate(LocalDate.now().atTime(endTimeOfDay()));
     }
 
     /**
@@ -3616,7 +3616,7 @@ public static Date endTimeOfToday() {
      * @return Date
      */
     public static Date startTimeOfLastMonth() {
-        return Converter.toDate(firstDayOfMonth(LocalDate.now().minusMonths(1)).atTime(startTimeOfDay()));
+        return toDate(firstDayOfMonth(LocalDate.now().minusMonths(1)).atTime(startTimeOfDay()));
     }
 
     /**
@@ -3625,7 +3625,7 @@ public static Date startTimeOfLastMonth() {
      * @return Date
      */
     public static Date endTimeOfLastMonth() {
-        return Converter.toDate(lastDayOfMonth(LocalDate.now().minusMonths(1)).atTime(endTimeOfDay()));
+        return toDate(lastDayOfMonth(LocalDate.now().minusMonths(1)).atTime(endTimeOfDay()));
     }
 
     /**
@@ -3634,7 +3634,7 @@ public static Date endTimeOfLastMonth() {
      * @return Date
      */
     public static Date startTimeOfMonth() {
-        return Converter.toDate(firstDayOfMonth(LocalDate.now()).atTime(startTimeOfDay()));
+        return toDate(firstDayOfMonth(LocalDate.now()).atTime(startTimeOfDay()));
     }
 
     /**
@@ -3643,7 +3643,7 @@ public static Date startTimeOfMonth() {
      * @return Date
      */
     public static Date endTimeOfMonth() {
-        return Converter.toDate(lastDayOfMonth(LocalDate.now()).atTime(endTimeOfDay()));
+        return toDate(lastDayOfMonth(LocalDate.now()).atTime(endTimeOfDay()));
     }
 
     /**
@@ -3653,7 +3653,7 @@ public static Date endTimeOfMonth() {
      * @return Date
      */
     public static Date startTimeOfDate(Date date) {
-        return Converter.toDate(Converter.toLocalDate(date).atTime(startTimeOfDay()));
+        return toDate(toLocalDate(date).atTime(startTimeOfDay()));
     }
 
     /**
@@ -3663,7 +3663,7 @@ public static Date startTimeOfDate(Date date) {
      * @return Date
      */
     public static Date endTimeOfDate(Date date) {
-        return Converter.toDate(Converter.toLocalDate(date).atTime(endTimeOfDay()));
+        return toDate(toLocalDate(date).atTime(endTimeOfDay()));
     }
 
 
@@ -3674,7 +3674,7 @@ public static Date endTimeOfDate(Date date) {
      * @return Date
      */
     public static Date endAccuracyTimeOfDate(Date date) {
-        return Converter.toDate(Converter.toLocalDate(date).atTime(endAccuracyTimeOfDay()));
+        return toDate(toLocalDate(date).atTime(endAccuracyTimeOfDay()));
     }
 
     /**
@@ -3706,7 +3706,7 @@ public static LocalDateTime endAccuracyTimeOfLocalDateTime(LocalDateTime localDa
      * @return Date
      */
     public static Date startTimeOfSpecialMonth(int year, int month) {
-        return Converter.toDate(LocalDate.of(year, month, 1).atTime(startTimeOfDay()));
+        return toDate(LocalDate.of(year, month, 1).atTime(startTimeOfDay()));
     }
 
     /**
@@ -3717,7 +3717,7 @@ public static Date startTimeOfSpecialMonth(int year, int month) {
      * @return Date
      */
     public static Date endTimeOfSpecialMonth(int year, int month) {
-        return Converter.toDate(lastDayOfMonth(LocalDate.of(year, month, 1)).atTime(endTimeOfDay()));
+        return toDate(lastDayOfMonth(LocalDate.of(year, month, 1)).atTime(endTimeOfDay()));
     }
 
     /**
@@ -3729,7 +3729,7 @@ public static Date endTimeOfSpecialMonth(int year, int month) {
      * @return Date
      */
     public static Date startTimeOfDate(int year, int month, int dayOfMonth) {
-        return Converter.toDate(LocalDate.of(year, month, dayOfMonth).atTime(startTimeOfDay()));
+        return toDate(LocalDate.of(year, month, dayOfMonth).atTime(startTimeOfDay()));
     }
 
     /**
@@ -3741,7 +3741,7 @@ public static Date startTimeOfDate(int year, int month, int dayOfMonth) {
      * @return Date
      */
     public static Date endTimeOfDate(int year, int month, int dayOfMonth) {
-        return Converter.toDate(LocalDate.of(year, month, dayOfMonth).atTime(endTimeOfDay()));
+        return toDate(LocalDate.of(year, month, dayOfMonth).atTime(endTimeOfDay()));
     }
 
     /**
@@ -3945,7 +3945,7 @@ public static boolean isSameDay(final Date date1, final Date date2) {
         if (date1 == null || date2 == null) {
             throw new IllegalArgumentException("The date must not be null");
         }
-        return isSameDay(Converter.toCalendar(date1), Converter.toCalendar(date2));
+        return isSameDay(toCalendar(date1), toCalendar(date2));
     }
 
     /**
@@ -4013,7 +4013,7 @@ public static boolean isSameWeek(final Date date1, final Date date2, boolean isM
         if (date1 == null || date2 == null) {
             throw new IllegalArgumentException("The date must not be null");
         }
-        return isSameWeek(Converter.toCalendar(date1), Converter.toCalendar(date2), isMon);
+        return isSameWeek(toCalendar(date1), toCalendar(date2), isMon);
     }
 
     /**
@@ -4027,7 +4027,7 @@ public static boolean isSameMonth(final Date date1, final Date date2) {
         if (date1 == null || date2 == null) {
             throw new IllegalArgumentException("The date must not be null");
         }
-        return isSameMonth(Converter.toCalendar(date1), Converter.toCalendar(date2));
+        return isSameMonth(toCalendar(date1), toCalendar(date2));
     }
 
     /**
@@ -4086,7 +4086,7 @@ public static boolean isSameMonthDay(LocalDate localDate1, LocalDate localDate2)
      * @return boolean
      */
     public static boolean isSameMonthDay(Date date, String monthDayStr) {
-        return isSameMonthDay(Converter.toLocalDate(date), monthDayStr);
+        return isSameMonthDay(toLocalDate(date), monthDayStr);
     }
 
     /**
@@ -4097,7 +4097,7 @@ public static boolean isSameMonthDay(Date date, String monthDayStr) {
      * @return boolean
      */
     public static boolean isSameMonthDay(Date date1, Date date2) {
-        return isSameMonthDay(Converter.toLocalDate(date1), Converter.toLocalDate(date2));
+        return isSameMonthDay(toLocalDate(date1), toLocalDate(date2));
     }
 
     /**
@@ -4162,7 +4162,7 @@ public static long betweenNextSameMonthDay(LocalDate localDate, String monthDayS
      */
     public static long betweenNextSameMonthDay(Date date, String monthDayStr) {
         MonthDay monthDay2 = MonthDay.parse(Symbol.MINUS + Symbol.MINUS + monthDayStr);
-        return betweenNextSameMonthDay(Converter.toLocalDate(date), monthDay2.getMonthValue(),
+        return betweenNextSameMonthDay(toLocalDate(date), monthDay2.getMonthValue(),
                 monthDay2.getDayOfMonth());
     }
 
@@ -4197,7 +4197,7 @@ public static LocalDate nextSameMonthDay(LocalDate localDate, String monthDayStr
      * @return Date
      */
     public static Date nextSameMonthDay(Date date, String monthDayStr) {
-        return Converter.toDate(nextSameMonthDay(Converter.toLocalDate(date), monthDayStr));
+        return toDate(nextSameMonthDay(toLocalDate(date), monthDayStr));
     }
 
     /**
@@ -4227,7 +4227,7 @@ public static String getZodiacCnName(String monthDay) {
      * @return String
      */
     public static String getZodiacCnName(Date date) {
-        return Fields.Zodiac.getCnNameByMonthDay(Formatter.format(date));
+        return Fields.Zodiac.getCnNameByMonthDay(format(date));
     }
 
     /**
@@ -4247,7 +4247,7 @@ public static String getZodiacEnName(String monthDay) {
      * @return String
      */
     public static String getZodiacEnName(Date date) {
-        return Fields.Zodiac.getEnNameByMonthDay(Formatter.format(date));
+        return Fields.Zodiac.getEnNameByMonthDay(format(date));
     }
 
     /**
@@ -4257,7 +4257,7 @@ public static String getZodiacEnName(Date date) {
      * @return 星座名
      */
     public static String getZodiac(Date date) {
-        return getZodiac(Converter.toCalendar(date));
+        return getZodiac(toCalendar(date));
     }
 
     /**
@@ -4311,8 +4311,8 @@ public static List getLocalDateTimeList(LocalDateTime startInclus
      * @return 时间列表
      */
     public static List getLocalDateList(LocalDate startInclusive, LocalDate endInclusive) {
-        return getLocalDateTimeList(Converter.toLocalDateTime(startInclusive),
-                Converter.toLocalDateTime(endInclusive)).stream()
+        return getLocalDateTimeList(toLocalDateTime(startInclusive),
+                toLocalDateTime(endInclusive)).stream()
                 .map(localDateTime -> localDateTime.toLocalDate()).collect(Collectors.toList());
     }
 
@@ -4325,7 +4325,7 @@ public static List getLocalDateList(LocalDate startInclusive, LocalDa
     public static List getLocalDateList(YearMonth yearMonth) {
         List localDateList = new ArrayList<>();
         long days = yearMonth.lengthOfMonth();
-        LocalDate localDate = Converter.toLocalDateStartOfMonth(yearMonth);
+        LocalDate localDate = toLocalDateStartOfMonth(yearMonth);
         for (long i = 0; i < days; i++) {
             localDateList.add(localDate.plusDays(i));
         }
@@ -4363,7 +4363,7 @@ public static List getLocalDateList(int year, int month) {
      */
     public static List getLocalDateTimeList(YearMonth yearMonth) {
         return getLocalDateList(yearMonth).stream()
-                .map(localDate -> Converter.toLocalDateTime(localDate)).collect(Collectors.toList());
+                .map(localDate -> toLocalDateTime(localDate)).collect(Collectors.toList());
     }
 
     /**
@@ -4374,7 +4374,7 @@ public static List getLocalDateTimeList(YearMonth yearMonth) {
      */
     public static List getLocalDateTimeList(String yearMonthStr) {
         return getLocalDateList(yearMonthStr).stream()
-                .map(localDate -> Converter.toLocalDateTime(localDate)).collect(Collectors.toList());
+                .map(localDate -> toLocalDateTime(localDate)).collect(Collectors.toList());
     }
 
     /**
@@ -4386,7 +4386,7 @@ public static List getLocalDateTimeList(String yearMonthStr) {
      */
     public static List getLocalDateTimeList(int year, int month) {
         return getLocalDateList(YearMonth.of(year, month)).stream()
-                .map(localDate -> Converter.toLocalDateTime(localDate)).collect(Collectors.toList());
+                .map(localDate -> toLocalDateTime(localDate)).collect(Collectors.toList());
     }
 
     /**
@@ -4396,7 +4396,7 @@ public static List getLocalDateTimeList(int year, int month) {
      * @return 时间列表
      */
     public static List getDateList(String yearMonthStr) {
-        return getLocalDateList(yearMonthStr).stream().map(localDate -> Converter.toDate(localDate))
+        return getLocalDateList(yearMonthStr).stream().map(localDate -> toDate(localDate))
                 .collect(Collectors.toList());
     }
 
@@ -4408,7 +4408,7 @@ public static List getDateList(String yearMonthStr) {
      * @return 时间列表
      */
     public static List getDateList(int year, int month) {
-        return getLocalDateList(YearMonth.of(year, month)).stream().map(localDate -> Converter.toDate(localDate))
+        return getLocalDateList(YearMonth.of(year, month)).stream().map(localDate -> toDate(localDate))
                 .collect(Collectors.toList());
     }
 
@@ -4420,9 +4420,9 @@ public static List getDateList(int year, int month) {
      * @return 时间列表
      */
     public static List getDateList(Date startInclusive, Date endInclusive) {
-        return getLocalDateTimeList(Converter.toLocalDateTime(startInclusive),
-                Converter.toLocalDateTime(endInclusive)).stream()
-                .map(localDateTime -> Converter.toDate(localDateTime)).collect(Collectors.toList());
+        return getLocalDateTimeList(toLocalDateTime(startInclusive),
+                toLocalDateTime(endInclusive)).stream()
+                .map(localDateTime -> toDate(localDateTime)).collect(Collectors.toList());
     }
 
     /**
@@ -4477,7 +4477,7 @@ public static boolean isBirthday(CharSequence value) {
      * @return boolean
      */
     public static boolean isBirthDay(Date date) {
-        return isBirthDay(Converter.toLocalDate(date));
+        return isBirthDay(toLocalDate(date));
     }
 
     /**
@@ -4497,7 +4497,7 @@ public static boolean isBirthDay(LocalDate localDate) {
      * @return boolean
      */
     public static boolean isBirthDay(LocalDateTime localDateTime) {
-        return isBirthDay(Converter.toLocalDate(localDateTime));
+        return isBirthDay(toLocalDate(localDateTime));
     }
 
     /**
@@ -4554,7 +4554,7 @@ public static LocalDateTime reduceAccuracyToSecond(LocalDateTime localDateTime)
      * @return Date
      */
     public static Date reduceAccuracyToSecond(Date date) {
-        return Converter.toDate(reduceAccuracyToSecond(Converter.toLocalDateTime(date)));
+        return toDate(reduceAccuracyToSecond(toLocalDateTime(date)));
     }
 
     /**
@@ -4576,7 +4576,7 @@ public static LocalDateTime reduceAccuracyToMinute(LocalDateTime localDateTime)
      * @return Date
      */
     public static Date reduceAccuracyToMinute(Date date) {
-        return Converter.toDate(reduceAccuracyToMinute(Converter.toLocalDateTime(date)));
+        return toDate(reduceAccuracyToMinute(toLocalDateTime(date)));
     }
 
     /**
@@ -4597,7 +4597,7 @@ public static LocalDateTime reduceAccuracyToHour(LocalDateTime localDateTime) {
      * @return Date
      */
     public static Date reduceAccuracyToHour(Date date) {
-        return Converter.toDate(reduceAccuracyToHour(Converter.toLocalDateTime(date)));
+        return toDate(reduceAccuracyToHour(toLocalDateTime(date)));
     }
 
     /**
@@ -4618,7 +4618,7 @@ public static LocalDateTime reduceAccuracyToDay(LocalDateTime localDateTime) {
      * @return Date
      */
     public static Date reduceAccuracyToDay(Date date) {
-        return Converter.toDate(reduceAccuracyToDay(Converter.toLocalDateTime(date)));
+        return toDate(reduceAccuracyToDay(toLocalDateTime(date)));
     }
 
     /**
@@ -4650,7 +4650,7 @@ public static int weekOfMonth(LocalDate localDate) {
      * @return 周数
      */
     public static int weekOfMonth(LocalDateTime localDateTime) {
-        return weekOfMonth(Converter.toLocalDate(localDateTime), null);
+        return weekOfMonth(toLocalDate(localDateTime), null);
     }
 
     /**
@@ -4660,7 +4660,7 @@ public static int weekOfMonth(LocalDateTime localDateTime) {
      * @return 周数
      */
     public static int weekOfMonth(Date date) {
-        return weekOfMonth(Converter.toLocalDate(date), null);
+        return weekOfMonth(toLocalDate(date), null);
     }
 
     /**
@@ -4701,7 +4701,7 @@ public static int weekOfYear(LocalDate localDate) {
      * @return 周数
      */
     public static int weekOfYear(LocalDateTime localDateTime) {
-        return weekOfYear(Converter.toLocalDate(localDateTime), null);
+        return weekOfYear(toLocalDate(localDateTime), null);
     }
 
     /**
@@ -4711,7 +4711,7 @@ public static int weekOfYear(LocalDateTime localDateTime) {
      * @return 周数
      */
     public static int weekOfYear(Date date) {
-        return weekOfYear(Converter.toLocalDate(date), null);
+        return weekOfYear(toLocalDate(date), null);
     }
 
     /**
@@ -4740,7 +4740,7 @@ public static boolean isMonday(LocalDate localDate) {
      * @return 是 true 否 false
      */
     public static boolean isMonday(Date date) {
-        return isMonday(Converter.toLocalDate(date));
+        return isMonday(toLocalDate(date));
     }
 
     /**
@@ -4760,7 +4760,7 @@ public static boolean isFriday(LocalDate localDate) {
      * @return 是 true 否 false
      */
     public static boolean isFriday(Date date) {
-        return isFriday(Converter.toLocalDate(date));
+        return isFriday(toLocalDate(date));
     }
 
     /**
@@ -4774,7 +4774,7 @@ public static boolean isDate(String dptDate, String pattern) {
         if (null == dptDate || dptDate.isEmpty()) {
             return false;
         }
-        String formatDate = Formatter.format(dptDate, pattern, pattern);
+        String formatDate = format(dptDate, pattern, pattern);
         return formatDate.equals(dptDate);
     }
 
@@ -4800,8 +4800,8 @@ public static boolean isBefore(String go, String back, String pattern) {
         if (null == go || null == back || go.isEmpty() || back.isEmpty())
             return false;
 
-        Date goDate = offsetDay(Formatter.parse(go, pattern), -1);
-        Date backDate = Formatter.parse(back, pattern);
+        Date goDate = offsetDay(parse(go, pattern), -1);
+        Date backDate = parse(back, pattern);
         if (null != goDate && null != backDate) {
             return goDate.before(backDate);
         }
@@ -4846,22 +4846,27 @@ public static boolean isShortDate(String date) {
 
     /**
      * 检查两个时间段是否有时间重叠
-     * 重叠指两个时间段是否有交集
-     *
-     * 
    - *
  1. x > b || a > y 无交集
  2. - *
  3. 则有交集的逻辑为 !(x > b || a > y) 根据德摩根公式,可化简为 x <= b && a <= y
  4. - *
+ * 重叠指两个时间段是否有交集,注意此方法时间段重合时如: + *
    + *
  • 此方法未纠正开始时间小于结束时间
  • + *
  • 当realStartTime和realEndTime或startTime和endTime相等时,退化为判断区间是否包含点
  • + *
  • 当realStartTime和realEndTime和startTime和endTime相等时,退化为判断点与点是否相等
  • + *
+ * See 准确的区间关系参考:艾伦区间代数 * * @param realStartTime 第一个时间段的开始时间 * @param realEndTime 第一个时间段的结束时间 * @param startTime 第二个时间段的开始时间 * @param endTime 第二个时间段的结束时间 - * @return true 表示时间有重合 + * @return true 表示时间有重合或包含或相等 */ - public static boolean isOverlap(ChronoLocalDateTime realStartTime, ChronoLocalDateTime realEndTime, - ChronoLocalDateTime startTime, ChronoLocalDateTime endTime) { - return startTime.isBefore(realEndTime) && endTime.isAfter(realStartTime); + public static boolean isOverlap(final Date realStartTime, final Date realEndTime, + final Date startTime, final Date endTime) { + + // x>b||a>y 无交集 + // 则有交集的逻辑为 !(x>b||a>y) + // 根据德摩根公式,可化简为 x<=b && a<=y 即 realStartTime<=endTime && startTime<=realEndTime + return realStartTime.compareTo(endTime) <= 0 && startTime.compareTo(realEndTime) <= 0; } /** @@ -5041,7 +5046,7 @@ public static String getChrono(LocalTime localTime) { * @return 十二时辰名称 */ public static String getChrono(LocalDateTime localDateTime) { - return Fields.Chrono.getName(Converter.toLocalTime(localDateTime)); + return Fields.Chrono.getName(toLocalTime(localDateTime)); } /** @@ -5070,7 +5075,7 @@ public static String getChrono() { * @return 星座名 */ public static String getAnimal(Date date) { - return getAnimal(Converter.toCalendar(date)); + return getAnimal(toCalendar(date)); } /** @@ -5126,60 +5131,6 @@ public static int getEndValue(Calendar calendar, int dateField) { return calendar.getActualMaximum(dateField); } - /** - * 转换为{@link DateTime}对象 - * - * @return 当前时间 - */ - public static DateTime date() { - return new DateTime(); - } - - /** - * {@link Date}类型时间转为{@link DateTime} - * - * @param date Long类型Date(Unix时间戳) - * @return 时间对象 - */ - public static DateTime date(Date date) { - if (date instanceof DateTime) { - return (DateTime) date; - } - return new DateTime(date); - } - - /** - * Long类型时间转为{@link DateTime} - * 只支持毫秒级别时间戳,如果需要秒级别时间戳,请自行×1000L - * - * @param date Long类型Date(Unix时间戳) - * @return 时间对象 - */ - public static DateTime date(long date) { - return new DateTime(date); - } - - /** - * {@link Calendar}类型时间转为{@link DateTime} - * - * @param calendar {@link Calendar} - * @return 时间对象 - */ - public static DateTime date(Calendar calendar) { - return new DateTime(calendar); - } - - /** - * {@link TemporalAccessor}类型时间转为{@link DateTime} - * 始终根据已有{@link TemporalAccessor} 产生新的{@link DateTime}对象 - * - * @param temporalAccessor {@link TemporalAccessor} - * @return 时间对象 - */ - public static DateTime date(TemporalAccessor temporalAccessor) { - return new DateTime(temporalAccessor); - } - /** * 修改日期为某个时间字段结束时间 * @@ -5572,13 +5523,67 @@ public static int getLastDayOfMonth(final Date date) { return date(date).getLastDayOfMonth(); } + /** + * 转换为{@link DateTime}对象 + * + * @return 当前时间 + */ + public static DateTime date() { + return new DateTime(); + } + + /** + * {@link Date}类型时间转为{@link DateTime} + * + * @param date Long类型Date(Unix时间戳) + * @return 时间对象 + */ + public static DateTime date(Date date) { + if (date instanceof DateTime) { + return (DateTime) date; + } + return new DateTime(date); + } + + /** + * Long类型时间转为{@link DateTime} + * 只支持毫秒级别时间戳,如果需要秒级别时间戳,请自行×1000L + * + * @param date Long类型Date(Unix时间戳) + * @return 时间对象 + */ + public static DateTime date(long date) { + return new DateTime(date); + } + + /** + * {@link Calendar}类型时间转为{@link DateTime} + * + * @param calendar {@link Calendar} + * @return 时间对象 + */ + public static DateTime date(Calendar calendar) { + return new DateTime(calendar); + } + + /** + * {@link TemporalAccessor}类型时间转为{@link DateTime} + * 始终根据已有{@link TemporalAccessor} 产生新的{@link DateTime}对象 + * + * @param temporalAccessor {@link TemporalAccessor} + * @return 时间对象 + */ + public static DateTime date(TemporalAccessor temporalAccessor) { + return new DateTime(temporalAccessor); + } + /** * 当前时间,格式 yyyy-MM-dd HH:mm:ss * * @return 当前时间的标准形式字符串 */ public static String now() { - return Formatter.format(date()); + return format(date()); } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/date/Formatter.java b/bus-core/src/main/java/org/aoju/bus/core/date/Formatter.java index c938a2543a..1e7b992581 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/date/Formatter.java +++ b/bus-core/src/main/java/org/aoju/bus/core/date/Formatter.java @@ -774,26 +774,6 @@ public static String getShotName(TimeUnit unit) { } } - /** - * 检查两个时间段是否有时间重叠 - * 重叠指两个时间段是否有交集 - * - *
    - *
  1. x > b || a > y 无交集
  2. - *
  3. 则有交集的逻辑为 !(x > b || a > y) 根据德摩根公式,可化简为 x <= b && a <= y
  4. - *
- * - * @param realStartTime 第一个时间段的开始时间 - * @param realEndTime 第一个时间段的结束时间 - * @param startTime 第二个时间段的开始时间 - * @param endTime 第二个时间段的结束时间 - * @return true 表示时间有重合 - */ - public static boolean isOverlap(Date realStartTime, Date realEndTime, - Date startTime, Date endTime) { - return startTime.before(realEndTime) && endTime.after(realStartTime); - } - /** * 标准化日期,默认处理以空格区分的日期时间格式,空格前为日期 * 将以下字符替换为"-" diff --git a/bus-core/src/main/java/org/aoju/bus/core/date/Lunar.java b/bus-core/src/main/java/org/aoju/bus/core/date/Lunar.java index a67d0def4f..dbb406d842 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/date/Lunar.java +++ b/bus-core/src/main/java/org/aoju/bus/core/date/Lunar.java @@ -2413,6 +2413,9 @@ public Lunar(int year, int month, int day, int hour, int minute, int second) { this.second = second; Solar noon = Solar.from(m.getFirstJulianDay() + day - 1); this.solar = Solar.from(noon.getYear(), noon.getMonth(), noon.getDay(), hour, minute, second); + if (noon.getYear() != year) { + y = Year.from(noon.getYear()); + } this.initialize(y); } @@ -4925,7 +4928,7 @@ public String getLiuYao() { * @return 物候 */ public String getWuHou() { - SolarTerm jieQi = getPrevJieQi(); + SolarTerm jieQi = getPrevJieQi(true); String name = jieQi.getName(); int offset = 0; for (int i = 0, j = Fields.CN_SOLARTERM.length; i < j; i++) { @@ -4934,13 +4937,13 @@ public String getWuHou() { break; } } - Calendar current = Kalendar.calendar(solar.getYear(), solar.getMonth(), solar.getDay()); - Solar startSolar = jieQi.getSolar(); - Calendar start = Kalendar.calendar(startSolar.getYear(), startSolar.getMonth(), startSolar.getDay()); - - int days = Solar.getDays(start, current); - return WU_HOU[offset * 3 + days / 5 % WU_HOU.length]; + int days = Solar.getDays(startSolar.getYear(), startSolar.getMonth(), startSolar.getDay(), solar.getYear(), solar.getMonth(), solar.getDay()); + int index = days / 5; + if (index > 2) { + index = 2; + } + return WU_HOU[(offset * 3 + index) % WU_HOU.length]; } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/date/StopWatch.java b/bus-core/src/main/java/org/aoju/bus/core/date/StopWatch.java index 7adbb66ca9..01e1ef2af7 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/date/StopWatch.java +++ b/bus-core/src/main/java/org/aoju/bus/core/date/StopWatch.java @@ -120,13 +120,22 @@ public StopWatch(String id, boolean keepTaskList) { } } + /** + * 创建计时任务(秒表) + * + * @return this + */ + public static StopWatch of() { + return new StopWatch(); + } + /** * 创建计时任务(秒表) * * @param id 用于标识秒表的唯一ID * @return this */ - public static StopWatch create(String id) { + public static StopWatch of(final String id) { return new StopWatch(id); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/date/formatter/parser/FastDateParser.java b/bus-core/src/main/java/org/aoju/bus/core/date/formatter/parser/FastDateParser.java index ec82c44563..b5cfe50c21 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/date/formatter/parser/FastDateParser.java +++ b/bus-core/src/main/java/org/aoju/bus/core/date/formatter/parser/FastDateParser.java @@ -51,7 +51,7 @@ public class FastDateParser extends AbstractMotd implements PositionDateParser { private static final long serialVersionUID = 1L; - private static final Locale JAPANESE_IMPERIAL = new Locale("ja", "JP", "JP"); + private static final Locale JAPANESE_IMPERIAL = Locale.of("ja", "JP", "JP"); // 用来对正则表达式排序的比较器。('february' 在 'feb'之前)所有条目按区域设置必须是小写的 private static final Comparator LONGER_FIRST_LOWERCASE = Comparator.reverseOrder(); private static final ConcurrentMap[] CACHES = new ConcurrentMap[Calendar.FIELD_COUNT]; diff --git a/bus-core/src/main/java/org/aoju/bus/core/image/Images.java b/bus-core/src/main/java/org/aoju/bus/core/image/Images.java index 3674e9f727..ab6c0d52cc 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/image/Images.java +++ b/bus-core/src/main/java/org/aoju/bus/core/image/Images.java @@ -571,6 +571,26 @@ public static BufferedImage makeBlur(BufferedImage srcImage, int radius) { return srcImage; } + /** + * 圆角 + * + * @param srcImage 图片流 + * @param width 宽度 + * @param height 高度 + * @param radius 半径 + * @return 图片流 + */ + public static BufferedImage makeRoundCorner(BufferedImage srcImage, int width, int height, int radius) { + BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = image.createGraphics(); + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.fillRoundRect(0, 0, width, height, radius, radius); + g.setComposite(AlphaComposite.SrcIn); + g.drawImage(srcImage, 0, 0, width, height, null); + g.dispose(); + return image; + } + /** * 将图片绘制在背景上 * @@ -1150,26 +1170,6 @@ public Images stroke(Color color, Stroke stroke) { return this; } - /** - * 圆角 - * - * @param srcImage 图片流 - * @param width 宽度 - * @param height 高度 - * @param radius 半径 - * @return 图片流 - */ - public static BufferedImage makeRoundCorner(BufferedImage srcImage, int width, int height, int radius) { - BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); - Graphics2D g = image.createGraphics(); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - g.fillRoundRect(0, 0, width, height, radius, radius); - g.setComposite(AlphaComposite.SrcIn); - g.drawImage(srcImage, 0, 0, width, height, null); - g.dispose(); - return image; - } - /** * 获取int类型的图片类型 * diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/Blending.java b/bus-core/src/main/java/org/aoju/bus/core/io/Blending.java old mode 100755 new mode 100644 diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/ByteString.java b/bus-core/src/main/java/org/aoju/bus/core/io/ByteString.java old mode 100755 new mode 100644 index b5f74ef706..168855f790 --- a/bus-core/src/main/java/org/aoju/bus/core/io/ByteString.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/ByteString.java @@ -51,21 +51,19 @@ */ public class ByteString implements Serializable, Comparable { + /** + * A singleton empty {@code ByteString}. + */ public static final ByteString EMPTY = ByteString.of(); - public transient int hashCode; - - private byte[] data; - private transient String utf8; - - public ByteString() { - - } + private static final long serialVersionUID = 1L; + public final byte[] data; + public transient int hashCode; // Lazily computed; 0 if unknown. + public transient String utf8; // Lazily computed. public ByteString(byte[] data) { - this.data = data; + this.data = data; // Trusted internal constructor doesn't clone data. } - public static ByteString of(byte... data) { if (null == data) { throw new IllegalArgumentException("data == null"); @@ -73,6 +71,10 @@ public static ByteString of(byte... data) { return new ByteString(data.clone()); } + /** + * Returns a new byte string containing a copy of {@code byteCount} bytes of {@code data} starting + * at {@code offset}. + */ public static ByteString of(byte[] data, int offset, int byteCount) { if (null == data) { throw new IllegalArgumentException("data == null"); @@ -94,6 +96,9 @@ public static ByteString of(ByteBuffer data) { return new ByteString(copy); } + /** + * Returns a new byte string containing the {@code UTF-8} bytes of {@code s}. + */ public static ByteString encodeUtf8(String s) { if (null == s) { throw new IllegalArgumentException("s == null"); @@ -103,6 +108,9 @@ public static ByteString encodeUtf8(String s) { return byteString; } + /** + * Returns a new byte string containing the {@code charset}-encoded bytes of {@code s}. + */ public static ByteString encodeString(String s, java.nio.charset.Charset charset) { if (null == s) { throw new IllegalArgumentException("s == null"); @@ -113,6 +121,10 @@ public static ByteString encodeString(String s, java.nio.charset.Charset charset return new ByteString(s.getBytes(charset)); } + /** + * Decodes the Base64-encoded bytes and returns their value as a byte string. + * Returns null if {@code base64} is not a Base64-encoded sequence of bytes. + */ public static ByteString decodeBase64(String base64) { if (null == base64) { throw new IllegalArgumentException("base64 == null"); @@ -121,6 +133,9 @@ public static ByteString decodeBase64(String base64) { return null != decoded ? new ByteString(decoded) : null; } + /** + * Decodes the hex-encoded bytes and returns their value a byte string. + */ public static ByteString decodeHex(String hex) { if (null == hex) { throw new IllegalArgumentException("hex == null"); @@ -145,6 +160,12 @@ private static int decodeHexDigit(char c) { throw new IllegalArgumentException("Unexpected hex digit: " + c); } + /** + * Reads {@code count} bytes from {@code in} and returns the result. + * + * @throws java.io.EOFException if {@code in} has fewer than {@code count} + * bytes to read. + */ public static ByteString read(InputStream in, int byteCount) throws IOException { if (null == in) { throw new IllegalArgumentException("in == null"); @@ -161,6 +182,24 @@ public static ByteString read(InputStream in, int byteCount) throws IOException return new ByteString(result); } + static int codePointIndexToCharIndex(String s, int codePointCount) { + for (int i = 0, j = 0, length = s.length(), c; i < length; i += Character.charCount(c)) { + if (j == codePointCount) { + return i; + } + c = s.codePointAt(i); + if ((Character.isISOControl(c) && c != '\n' && c != '\r') + || c == Buffer.REPLACEMENT_CHARACTER) { + return -1; + } + j++; + } + return s.length(); + } + + /** + * Constructs a new {@code String} by decoding the bytes as {@code UTF-8}. + */ public String utf8() { String result = utf8; // We don't care if we double-allocate in racy code. @@ -174,22 +213,39 @@ public String string(java.nio.charset.Charset charset) { return new String(data, charset); } + /** + * Returns this byte string encoded as Base64. In violation of the + * RFC, the returned string does not wrap lines at 76 columns. + */ public String base64() { return Base64.encode(data); } + /** + * Returns the 128-bit MD5 hash of this byte string. + */ public ByteString md5() { return digest(Algorithm.MD5.getValue()); } + /** + * Returns the 160-bit SHA-1 hash of this byte string. + */ public ByteString sha1() { return digest(Algorithm.SHA1.getValue()); } + /** + * Returns the 256-bit SHA-256 hash of this byte string. + */ public ByteString sha256() { return digest(Algorithm.SHA256.getValue()); } + /** + * Returns the 512-bit SHA-512 hash of this byte string. + */ public ByteString sha512() { return digest(Algorithm.SHA512.getValue()); } @@ -202,14 +258,23 @@ private ByteString digest(String algorithm) { } } + /** + * Returns the 160-bit SHA-1 HMAC of this byte string. + */ public ByteString hmacSha1(ByteString key) { return hmac(Algorithm.HMACSHA1.getValue(), key); } + /** + * Returns the 256-bit SHA-256 HMAC of this byte string. + */ public ByteString hmacSha256(ByteString key) { return hmac(Algorithm.HMACSHA256.getValue(), key); } + /** + * Returns the 512-bit SHA-512 HMAC of this byte string. + */ public ByteString hmacSha512(ByteString key) { return hmac(Algorithm.HMACSHA512.getValue(), key); } @@ -226,10 +291,17 @@ private ByteString hmac(String algorithm, ByteString key) { } } + /** + * Returns this byte string encoded as URL-safe + * Base64. + */ public String base64Url() { return Base64.encodeUrlSafe(data); } + /** + * Returns this byte string encoded in hexadecimal. + */ public String hex() { char[] result = new char[data.length * 2]; int c = 0; @@ -240,8 +312,13 @@ public String hex() { return new String(result); } + /** + * Returns a byte string equal to this byte string, but with the bytes 'A' + * through 'Z' replaced with the corresponding byte in 'a' through 'z'. + * Returns this byte string if it contains no bytes in 'A' through 'Z'. + */ public ByteString toAsciiLowercase() { - // Search for an uppercase character. If we don't find first, return this. + // Search for an uppercase character. If we don't find one, return this. for (int i = 0; i < data.length; i++) { byte c = data[i]; if (c < 'A' || c > 'Z') continue; @@ -260,8 +337,13 @@ public ByteString toAsciiLowercase() { return this; } + /** + * Returns a byte string equal to this byte string, but with the bytes 'a' + * through 'z' replaced with the corresponding byte in 'A' through 'Z'. + * Returns this byte string if it contains no bytes in 'a' through 'z'. + */ public ByteString toAsciiUppercase() { - // Search for an lowercase character. If we don't find first, return this. + // Search for an lowercase character. If we don't find one, return this. for (int i = 0; i < data.length; i++) { byte c = data[i]; if (c < 'a' || c > 'z') continue; @@ -280,10 +362,19 @@ public ByteString toAsciiUppercase() { return this; } + /** + * Returns a byte string that is a substring of this byte string, beginning at the specified + * index until the end of this string. Returns this byte string if {@code beginIndex} is 0. + */ public ByteString substring(int beginIndex) { return substring(beginIndex, data.length); } + /** + * Returns a byte string that is a substring of this byte string, beginning at the specified + * {@code beginIndex} and ends at the specified {@code endIndex}. Returns this byte string if + * {@code beginIndex} is 0 and {@code endIndex} is the length of this byte string. + */ public ByteString substring(int beginIndex, int endIndex) { if (beginIndex < 0) throw new IllegalArgumentException("beginIndex < 0"); if (endIndex > data.length) { @@ -302,41 +393,70 @@ public ByteString substring(int beginIndex, int endIndex) { return new ByteString(copy); } + /** + * Returns the byte at {@code pos}. + */ public byte getByte(int pos) { return data[pos]; } + /** + * Returns the number of bytes in this ByteString. + */ public int size() { return data.length; } + /** + * Returns a byte array containing a copy of the bytes in this {@code ByteString}. + */ public byte[] toByteArray() { return data.clone(); } + /** + * Returns the bytes of this string without a defensive copy. Do not mutate! + */ public byte[] internalArray() { return data; } + /** + * Returns a {@code ByteBuffer} view of the bytes in this {@code ByteString}. + */ public ByteBuffer asByteBuffer() { return ByteBuffer.wrap(data).asReadOnlyBuffer(); } + /** + * Writes the contents of this byte string to {@code out}. + */ public void write(OutputStream out) throws IOException { - if (null == out) { - throw new IllegalArgumentException("out == null"); - } + if (out == null) throw new IllegalArgumentException("out == null"); out.write(data); } + /** + * Writes the contents of this byte string to {@code buffer}. + */ public void write(Buffer buffer) { buffer.write(data, 0, data.length); } + /** + * Returns true if the bytes of this in {@code [offset..offset+byteCount)} equal the bytes of + * {@code other} in {@code [otherOffset..otherOffset+byteCount)}. Returns false if either range is + * out of bounds. + */ public boolean rangeEquals(int offset, ByteString other, int otherOffset, int byteCount) { return other.rangeEquals(otherOffset, this.data, offset, byteCount); } + /** + * Returns true if the bytes of this in {@code [offset..offset+byteCount)} equal the bytes of + * {@code other} in {@code [otherOffset..otherOffset+byteCount)}. Returns false if either range is + * out of bounds. + */ public boolean rangeEquals(int offset, byte[] other, int otherOffset, int byteCount) { return offset >= 0 && offset <= data.length - byteCount && otherOffset >= 0 && otherOffset <= other.length - byteCount diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/FileSystem.java b/bus-core/src/main/java/org/aoju/bus/core/io/FileSystem.java new file mode 100644 index 0000000000..9a8b8056c2 --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/io/FileSystem.java @@ -0,0 +1,170 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.io; + +import org.aoju.bus.core.io.sink.Sink; +import org.aoju.bus.core.io.source.Source; +import org.aoju.bus.core.toolkit.IoKit; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +/** + * Access to read and write files on a hierarchical data store. Most callers should use the {@link + * #SYSTEM} implementation, which uses the host machine's local file system. Alternate + * implementations may be used to inject faults (for testing) or to transform stored data (to add + * encryption, for example). + * + *

All operations on a file system are racy. For example, guarding a call to {@link #source} with + * {@link #exists} does not guarantee that {@link FileNotFoundException} will not be thrown. The + * file may be moved between the two calls! + * + *

This interface is less ambitious than {@link java.nio.file.FileSystem} introduced in Java 7. + * It lacks important features like file watching, metadata, permissions, and disk space + * information. In exchange for these limitations, this interface is easier to implement and works + * on all versions of Java and Android. + */ +public interface FileSystem { + + /** + * The host machine's local file system. + */ + FileSystem SYSTEM = new FileSystem() { + @Override + public Source source(File file) throws FileNotFoundException { + return IoKit.source(file); + } + + @Override + public Sink sink(File file) throws FileNotFoundException { + try { + return IoKit.sink(file); + } catch (FileNotFoundException e) { + // Maybe the parent directory doesn't exist? Try creating it first. + file.getParentFile().mkdirs(); + return IoKit.sink(file); + } + } + + @Override + public Sink appendingSink(File file) throws FileNotFoundException { + try { + return IoKit.appendingSink(file); + } catch (FileNotFoundException e) { + // Maybe the parent directory doesn't exist? Try creating it first. + file.getParentFile().mkdirs(); + return IoKit.appendingSink(file); + } + } + + @Override + public void delete(File file) throws IOException { + // If delete() fails, make sure it's because the file didn't exist! + if (!file.delete() && file.exists()) { + throw new IOException("failed to delete " + file); + } + } + + @Override + public boolean exists(File file) { + return file.exists(); + } + + @Override + public long size(File file) { + return file.length(); + } + + @Override + public void rename(File from, File to) throws IOException { + delete(to); + if (!from.renameTo(to)) { + throw new IOException("failed to rename " + from + " to " + to); + } + } + + @Override + public void deleteContents(File directory) throws IOException { + File[] files = directory.listFiles(); + if (files == null) { + throw new IOException("not a readable directory: " + directory); + } + for (File file : files) { + if (file.isDirectory()) { + deleteContents(file); + } + if (!file.delete()) { + throw new IOException("failed to delete " + file); + } + } + } + }; + + /** + * Reads from {@code file}. + */ + Source source(File file) throws FileNotFoundException; + + /** + * Writes to {@code file}, discarding any data already present. Creates parent directories if + * necessary. + */ + Sink sink(File file) throws FileNotFoundException; + + /** + * Writes to {@code file}, appending if data is already present. Creates parent directories if + * necessary. + */ + Sink appendingSink(File file) throws FileNotFoundException; + + /** + * Deletes {@code file} if it exists. Throws if the file exists and cannot be deleted. + */ + void delete(File file) throws IOException; + + /** + * Returns true if {@code file} exists on the file system. + */ + boolean exists(File file); + + /** + * Returns the number of bytes stored in {@code file}, or 0 if it does not exist. + */ + long size(File file); + + /** + * Renames {@code from} to {@code to}. Throws if the file cannot be renamed. + */ + void rename(File from, File to) throws IOException; + + /** + * Recursively delete the contents of {@code directory}. Throws an IOException if any file could + * not be deleted, or if {@code dir} is not a readable directory. + */ + void deleteContents(File directory) throws IOException; + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/LifeCycle.java b/bus-core/src/main/java/org/aoju/bus/core/io/LifeCycle.java old mode 100755 new mode 100644 index 980899c2a5..6fab06d746 --- a/bus-core/src/main/java/org/aoju/bus/core/io/LifeCycle.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/LifeCycle.java @@ -36,13 +36,23 @@ */ public final class LifeCycle { - static final long MAX_SIZE = Normal._64 * Normal._1024; + /** + * The maximum number of bytes to pool + * 64 KiB + */ + public static final long MAX_SIZE = Normal._64 * Normal._1024; - static Segment next; + /** + * Singly-linked list of segments + */ + public static Segment next; - static long byteCount; + /** + * Total bytes in this pool + */ + public static long byteCount; - private LifeCycle() { + public LifeCycle() { } @@ -56,14 +66,14 @@ public static Segment take() { return result; } } - return new Segment(); + return new Segment(); // Pool is empty. Don't zero-fill while holding a lock. } public static void recycle(Segment segment) { - if (null != segment.next || null != segment.prev) throw new IllegalArgumentException(); - if (segment.shared) return; + if (segment.next != null || segment.prev != null) throw new IllegalArgumentException(); + if (segment.shared) return; // This segment cannot be recycled. synchronized (LifeCycle.class) { - if (byteCount + Segment.SIZE > MAX_SIZE) return; + if (byteCount + Segment.SIZE > MAX_SIZE) return; // Pool is full. byteCount += Segment.SIZE; segment.next = next; segment.pos = segment.limit = 0; diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/Segment.java b/bus-core/src/main/java/org/aoju/bus/core/io/Segment.java old mode 100755 new mode 100644 index 75a9f7fd66..5782f62bf2 --- a/bus-core/src/main/java/org/aoju/bus/core/io/Segment.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/Segment.java @@ -99,15 +99,27 @@ public Segment(byte[] data, int pos, int limit, boolean shared, boolean owner) { this.owner = owner; } + /** + * Returns a new segment that shares the underlying byte array with this. Adjusting pos and limit + * are safe but writes are forbidden. This also marks the current segment as shared, which + * prevents it from being pooled. + */ public final Segment sharedCopy() { shared = true; return new Segment(data, pos, limit, true, false); } + /** + * Returns a new segment that its own private copy of the underlying byte array. + */ public final Segment unsharedCopy() { return new Segment(data.clone(), pos, limit, false, true); } + /** + * Removes this segment of a circularly-linked list and returns its successor. + * Returns null if the list is now empty. + */ public final Segment pop() { Segment result = next != this ? next : null; prev.next = next; @@ -117,7 +129,11 @@ public final Segment pop() { return result; } - public Segment push(Segment segment) { + /** + * Appends {@code segment} after this segment in the circularly-linked list. + * Returns the pushed segment. + */ + public final Segment push(Segment segment) { segment.prev = this; segment.next = next; next.prev = segment; @@ -125,10 +141,23 @@ public Segment push(Segment segment) { return segment; } - public Segment split(int byteCount) { + /** + * Splits this head of a circularly-linked list into two segments. The first + * segment contains the data in {@code [pos..pos+byteCount)}. The second + * segment contains the data in {@code [pos+byteCount..limit)}. This can be + * useful when moving partial segments from one buffer to another. + * + *

Returns the new head of the circularly-linked list. + */ + public final Segment split(int byteCount) { if (byteCount <= 0 || byteCount > limit - pos) throw new IllegalArgumentException(); Segment prefix; + // We have two competing performance goals: + // - Avoid copying data. We accomplish this by sharing segments. + // - Avoid short shared segments. These are bad for performance because they are readonly and + // may lead to long chains of short segments. + // To balance these goals we only share segments when the copy will be large. if (byteCount >= SHARE_MINIMUM) { prefix = sharedCopy(); } else { @@ -159,9 +188,13 @@ public void compact() { LifeCycle.recycle(this); } - public void writeTo(Segment sink, int byteCount) { + /** + * Moves {@code byteCount} bytes from this segment to {@code sink}. + */ + public final void writeTo(Segment sink, int byteCount) { if (!sink.owner) throw new IllegalArgumentException(); if (sink.limit + byteCount > SIZE) { + // We can't fit byteCount bytes at the sink's current position. Shift sink first. if (sink.shared) throw new IllegalArgumentException(); if (sink.limit + byteCount - sink.pos > SIZE) throw new IllegalArgumentException(); System.arraycopy(sink.data, sink.pos, sink.data, 0, sink.limit - sink.pos); diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/buffer/Buffer.java b/bus-core/src/main/java/org/aoju/bus/core/io/buffer/Buffer.java old mode 100755 new mode 100644 index 542d809ea2..5d5e9e3f4e --- a/bus-core/src/main/java/org/aoju/bus/core/io/buffer/Buffer.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/buffer/Buffer.java @@ -337,6 +337,11 @@ private void readFrom(InputStream in, long byteCount, boolean forever) throws IO int maxToCopy = (int) Math.min(byteCount, Segment.SIZE - tail.limit); int bytesRead = in.read(tail.data, tail.limit, maxToCopy); if (bytesRead == -1) { + if (tail.pos == tail.limit) { + // We allocated a tail segment, but didn't end up needing it. Recycle! + head = tail.pop(); + LifeCycle.recycle(tail); + } if (forever) return; throw new EOFException(); } @@ -346,6 +351,11 @@ private void readFrom(InputStream in, long byteCount, boolean forever) throws IO } } + /** + * Returns the number of bytes in segments that are not writable. This is the + * number of bytes that can be flushed immediately to an underlying sink + * without harming throughput. + */ public final long completeSegmentByteCount() { long result = size; if (result == 0) return 0; @@ -537,6 +547,7 @@ public long readDecimalLong() { if (b >= Symbol.C_ZERO && b <= Symbol.C_NINE) { int digit = Symbol.C_ZERO - b; + // Detect when the digit would cause an overflow. if (value < overflowZone || value == overflowZone && digit < overflowDigit) { Buffer buffer = new Buffer().writeDecimalLong(value).writeByte(b); if (!negative) buffer.readByte(); // Skip negative sign. @@ -552,6 +563,7 @@ public long readDecimalLong() { throw new NumberFormatException( "Expected leading [0-9] or '-' character but was 0x" + Integer.toHexString(b)); } + // Set a flag to stop iteration. We still need to run through segment updating below. done = true; break; } @@ -563,7 +575,7 @@ public long readDecimalLong() { } else { segment.pos = pos; } - } while (!done && null != head); + } while (!done && head != null); size -= seen; return negative ? value : -value; @@ -599,10 +611,12 @@ public long readHexadecimalUnsignedLong() { throw new NumberFormatException( "Expected leading [0-9a-fA-F] character but was 0x" + Integer.toHexString(b)); } + // Set a flag to stop iteration. We still need to run through segment updating below. done = true; break; } + // Detect when the shift will overflow. if ((value & 0xf000000000000000L) != 0) { Buffer buffer = new Buffer().writeHexadecimalUnsignedLong(value).writeByte(b); throw new NumberFormatException("Number too large: " + buffer.readUtf8()); @@ -635,11 +649,12 @@ public ByteString readByteString(long byteCount) throws EOFException { } @Override - public int select(Blending options) { - int index = selectPrefix(options, false); + public int select(Blending blending) { + int index = selectPrefix(blending, false); if (index == -1) return -1; - int selectedSize = options.byteStrings[index].size(); + // If the prefix match actually matched a full byte string, consume it and return it. + int selectedSize = blending.byteStrings[index].size(); try { skip(selectedSize); } catch (EOFException e) { @@ -658,11 +673,11 @@ public int select(Blending options) { * 请注意,由于选项是按优先顺序列出的,而且第一个选项可能是另一个选项的前缀, * 这使得情况变得复杂。例如,如果缓冲区包含[ab]而选项是[abc, a],则返回-2 */ - public int selectPrefix(Blending options, boolean selectTruncated) { + public int selectPrefix(Blending blending, boolean selectTruncated) { Segment head = this.head; - if (null == head) { - if (selectTruncated) return -2; - return options.indexOf(ByteString.EMPTY); + if (head == null) { + if (selectTruncated) return -2; // A result is present but truncated. + return blending.indexOf(ByteString.EMPTY); } Segment s = head; @@ -670,7 +685,7 @@ public int selectPrefix(Blending options, boolean selectTruncated) { int pos = head.pos; int limit = head.limit; - int[] trie = options.trie; + int[] trie = blending.trie; int triePos = 0; int prefixIndex = -1; @@ -686,24 +701,26 @@ public int selectPrefix(Blending options, boolean selectTruncated) { int nextStep; - if (null == s) { + if (s == null) { break; } else if (scanOrSelect < 0) { + // Scan: take multiple bytes from the buffer and the trie, looking for any mismatch. int scanByteCount = -1 * scanOrSelect; int trieLimit = triePos + scanByteCount; while (true) { int b = data[pos++] & 0xff; - if (b != trie[triePos++]) return prefixIndex; + if (b != trie[triePos++]) return prefixIndex; // Fail 'cause we found a mismatch. boolean scanComplete = (triePos == trieLimit); + // Advance to the next buffer segment if this one is exhausted. if (pos == limit) { s = s.next; pos = s.pos; data = s.data; limit = s.limit; if (s == head) { - if (!scanComplete) break navigateTrie; - s = null; + if (!scanComplete) break navigateTrie; // We were exhausted before the scan completed. + s = null; // We were exhausted at the end of the scan. } } @@ -713,11 +730,12 @@ public int selectPrefix(Blending options, boolean selectTruncated) { } } } else { + // Select: take one byte from the buffer and find a match in the trie. int selectChoiceCount = scanOrSelect; int b = data[pos++] & 0xff; int selectLimit = triePos + selectChoiceCount; while (true) { - if (triePos == selectLimit) return prefixIndex; + if (triePos == selectLimit) return prefixIndex; // Fail 'cause we didn't find a match. if (b == trie[triePos]) { nextStep = trie[triePos + selectChoiceCount]; @@ -727,30 +745,31 @@ public int selectPrefix(Blending options, boolean selectTruncated) { triePos++; } + // Advance to the next buffer segment if this one is exhausted. if (pos == limit) { s = s.next; pos = s.pos; data = s.data; limit = s.limit; if (s == head) { - s = null; + s = null; // No more segments! The next trie node will be our last. } } } - if (nextStep >= 0) return nextStep; - triePos = -nextStep; + if (nextStep >= 0) return nextStep; // Found a matching option. + triePos = -nextStep; // Found another node to continue the search. } - - if (selectTruncated) return -2; - return prefixIndex; + // We break out of the loop above when we've exhausted the buffer without exhausting the trie. + if (selectTruncated) return -2; // The buffer is a prefix of at least one option. + return prefixIndex; // Return any matches we encountered while searching for a deeper match. } @Override public void readFully(Buffer sink, long byteCount) throws EOFException { if (size < byteCount) { - sink.write(this, size); + sink.write(this, size); // Exhaust ourselves. throw new EOFException(); } sink.write(this, byteCount); @@ -801,6 +820,7 @@ public String readString(long byteCount, java.nio.charset.Charset charset) throw Segment s = head; if (s.pos + byteCount > s.limit) { + // If the string spans multiple segments, delegate to readBytes(). return new String(readByteArray(byteCount), charset); } @@ -1084,42 +1104,54 @@ public Buffer writeUtf8(String string, int beginIndex, int endIndex) { int segmentOffset = tail.limit - i; int runLimit = Math.min(endIndex, Segment.SIZE - segmentOffset); - data[segmentOffset + i++] = (byte) c; + // Emit a 7-bit character with 1 byte. + data[segmentOffset + i++] = (byte) c; // 0xxxxxxx + // Fast-path contiguous runs of ASCII characters. This is ugly, but yields a ~4x performance + // improvement over independent calls to writeByte(). while (i < runLimit) { c = string.charAt(i); if (c >= 0x80) break; - data[segmentOffset + i++] = (byte) c; + data[segmentOffset + i++] = (byte) c; // 0xxxxxxx } - int runSize = i + segmentOffset - tail.limit; + int runSize = i + segmentOffset - tail.limit; // Equivalent to i - (previous i). tail.limit += runSize; size += runSize; } else if (c < 0x800) { - writeByte(c >> 6 | 0xc0); - writeByte(c & 0x3f | 0x80); + // Emit a 11-bit character with 2 bytes. + writeByte(c >> 6 | 0xc0); // 110xxxxx + writeByte(c & 0x3f | 0x80); // 10xxxxxx i++; } else if (c < 0xd800 || c > 0xdfff) { - writeByte(c >> 12 | 0xe0); - writeByte(c >> 6 & 0x3f | 0x80); - writeByte(c & 0x3f | 0x80); + // Emit a 16-bit character with 3 bytes. + writeByte(c >> 12 | 0xe0); // 1110xxxx + writeByte(c >> 6 & 0x3f | 0x80); // 10xxxxxx + writeByte(c & 0x3f | 0x80); // 10xxxxxx i++; } else { + // c is a surrogate. Make sure it is a high surrogate & that its successor is a low + // surrogate. If not, the UTF-16 is invalid, in which case we emit a replacement character. int low = i + 1 < endIndex ? string.charAt(i + 1) : 0; if (c > 0xdbff || low < 0xdc00 || low > 0xdfff) { - writeByte(Symbol.C_QUESTION_MARK); + writeByte('?'); i++; continue; } + + // UTF-16 high surrogate: 110110xxxxxxxxxx (10 bits) + // UTF-16 low surrogate: 110111yyyyyyyyyy (10 bits) + // Unicode code point: 00010000000000000000 + xxxxxxxxxxyyyyyyyyyy (21 bits) int codePoint = 0x010000 + ((c & ~0xd800) << 10 | low & ~0xdc00); - writeByte(codePoint >> 18 | 0xf0); - writeByte(codePoint >> 12 & 0x3f | 0x80); - writeByte(codePoint >> 6 & 0x3f | 0x80); - writeByte(codePoint & 0x3f | 0x80); + // Emit a 21-bit character with 4 bytes. + writeByte(codePoint >> 18 | 0xf0); // 11110xxx + writeByte(codePoint >> 12 & 0x3f | 0x80); // 10xxxxxx + writeByte(codePoint >> 6 & 0x3f | 0x80); // 10xxyyyy + writeByte(codePoint & 0x3f | 0x80); // 10yyyyyy i += 2; } } @@ -1130,26 +1162,31 @@ public Buffer writeUtf8(String string, int beginIndex, int endIndex) { @Override public Buffer writeUtf8CodePoint(int codePoint) { if (codePoint < 0x80) { + // Emit a 7-bit code point with 1 byte. writeByte(codePoint); } else if (codePoint < 0x800) { - writeByte(codePoint >> 6 | 0xc0); - writeByte(codePoint & 0x3f | 0x80); + // Emit a 11-bit code point with 2 bytes. + writeByte(codePoint >> 6 | 0xc0); // 110xxxxx + writeByte(codePoint & 0x3f | 0x80); // 10xxxxxx } else if (codePoint < 0x10000) { if (codePoint >= 0xd800 && codePoint <= 0xdfff) { - writeByte(Symbol.C_QUESTION_MARK); + // Emit a replacement character for a partial surrogate. + writeByte('?'); } else { - writeByte(codePoint >> 12 | 0xe0); - writeByte(codePoint >> 6 & 0x3f | 0x80); - writeByte(codePoint & 0x3f | 0x80); + // Emit a 16-bit code point with 3 bytes. + writeByte(codePoint >> 12 | 0xe0); // 1110xxxx + writeByte(codePoint >> 6 & 0x3f | 0x80); // 10xxxxxx + writeByte(codePoint & 0x3f | 0x80); // 10xxxxxx } } else if (codePoint <= 0x10ffff) { - writeByte(codePoint >> 18 | 0xf0); - writeByte(codePoint >> 12 & 0x3f | 0x80); - writeByte(codePoint >> 6 & 0x3f | 0x80); - writeByte(codePoint & 0x3f | 0x80); + // Emit a 21-bit code point with 4 bytes. + writeByte(codePoint >> 18 | 0xf0); // 11110xxx + writeByte(codePoint >> 12 & 0x3f | 0x80); // 10xxxxxx + writeByte(codePoint >> 6 & 0x3f | 0x80); // 10xxxxxx + writeByte(codePoint & 0x3f | 0x80); // 10xxxxxx } else { throw new IllegalArgumentException( @@ -1434,23 +1471,28 @@ public void write(Buffer source, long byteCount) { IoKit.checkOffsetAndCount(source.size, 0, byteCount); while (byteCount > 0) { + // Is a prefix of the source's head segment all that we need to move? if (byteCount < (source.head.limit - source.head.pos)) { - Segment tail = null != head ? head.prev : null; - if (null != tail && tail.owner + Segment tail = head != null ? head.prev : null; + if (tail != null && tail.owner && (byteCount + tail.limit - (tail.shared ? 0 : tail.pos) <= Segment.SIZE)) { + // Our existing segments are sufficient. Move bytes from source's head to our tail. source.head.writeTo(tail, (int) byteCount); source.size -= byteCount; size += byteCount; return; } else { + // We're going to need another segment. Split the source's head + // segment in two, then move the first of those two to this buffer. source.head = source.head.split((int) byteCount); } } + // Remove the source's head segment and append it to our tail. Segment segmentToMove = source.head; long movedByteCount = segmentToMove.limit - segmentToMove.pos; source.head = segmentToMove.pop(); - if (null == head) { + if (head == null) { head = segmentToMove; head.next = head.prev = head; } else { @@ -1487,6 +1529,10 @@ public long indexOf(byte b) { return indexOf(b, 0, Long.MAX_VALUE); } + /** + * Returns the index of {@code b} in this at or beyond {@code fromIndex}, or + * -1 if this buffer does not contain {@code b} in that range. + */ @Override public long indexOf(byte b, long fromIndex) { return indexOf(b, fromIndex, Long.MAX_VALUE); @@ -1556,7 +1602,6 @@ public long indexOf(ByteString bytes, long fromIndex) throws IOException { Segment s; long offset; - // TODO(jwilson): extract this to a shared helper method when can do so without allocating. findSegmentAndOffset: { s = head; @@ -1589,6 +1634,7 @@ public long indexOf(ByteString bytes, long fromIndex) throws IOException { } } + // Not in this segment. Try the next one. offset += (s.limit - s.pos); fromIndex = offset; s = s.next; @@ -1630,7 +1676,11 @@ public long indexOfElement(ByteString targetBytes, long fromIndex) { } } + // Special case searching for one of two bytes. This is a common case for tools like Moshi, + // which search for pairs of chars like `\r` and `\n` or {@code `"` and `\`. The impact of this + // optimization is a ~5x speedup for this case without a substantial cost to other cases. if (targetBytes.size() == 2) { + // Scan through the segments, searching for either of the two bytes. byte b0 = targetBytes.getByte(0); byte b1 = targetBytes.getByte(1); while (offset < size) { @@ -1642,11 +1692,13 @@ public long indexOfElement(ByteString targetBytes, long fromIndex) { } } + // Not in this segment. Try the next one. offset += (s.limit - s.pos); fromIndex = offset; s = s.next; } } else { + // Scan through the segments, searching for a byte that's also in the array. byte[] targetByteArray = targetBytes.internalArray(); while (offset < size) { byte[] data = s.data; @@ -1657,6 +1709,7 @@ public long indexOfElement(ByteString targetBytes, long fromIndex) { } } + // Not in this segment. Try the next one. offset += (s.limit - s.pos); fromIndex = offset; s = s.next; @@ -1735,6 +1788,9 @@ public Timeout timeout() { return Timeout.NONE; } + /** + * For testing. This returns the sizes of the segments in this buffer. + */ List segmentSizes() { if (null == head) { return Collections.emptyList(); @@ -1750,28 +1806,28 @@ List segmentSizes() { /** * @return the 128-bit MD5 hash of this buffer. */ - public final ByteString md5() { + public ByteString md5() { return digest(Algorithm.MD5.getValue()); } /** * @return the 160-bit SHA-1 hash of this buffer. */ - public final ByteString sha1() { + public ByteString sha1() { return digest(Algorithm.SHA1.getValue()); } /** * @return the 256-bit SHA-256 hash of this buffer. */ - public final ByteString sha256() { + public ByteString sha256() { return digest(Algorithm.SHA256.getValue()); } /** * @return the 512-bit SHA-512 hash of this buffer. */ - public final ByteString sha512() { + public ByteString sha512() { return digest(Algorithm.SHA512.getValue()); } @@ -1794,7 +1850,7 @@ private ByteString digest(String algorithm) { * @param key ByteString * @return the 160-bit SHA-1 HMAC of this buffer. */ - public final ByteString hmacSha1(ByteString key) { + public ByteString hmacSha1(ByteString key) { return hmac(Algorithm.HMACSHA1.getValue(), key); } @@ -1802,7 +1858,7 @@ public final ByteString hmacSha1(ByteString key) { * @param key ByteString * @return the 256-bit SHA-256 HMAC of this buffer. */ - public final ByteString hmacSha256(ByteString key) { + public ByteString hmacSha256(ByteString key) { return hmac(Algorithm.HMACSHA256.getValue(), key); } @@ -1882,11 +1938,18 @@ public int hashCode() { return result; } + /** + * Returns a human-readable string that describes the contents of this buffer. Typically this + * is a string like {@code [text=Hello]} or {@code [hex=0000ffff]}. + */ @Override public String toString() { return snapshot().toString(); } + /** + * Returns a deep copy of this buffer. + */ @Override public Buffer clone() { Buffer result = new Buffer(); @@ -1901,23 +1964,29 @@ public Buffer clone() { return result; } - public final ByteString snapshot() { + /** + * Returns an immutable copy of this buffer as a byte string. + */ + public ByteString snapshot() { if (size > Integer.MAX_VALUE) { throw new IllegalArgumentException("size > Integer.MAX_VALUE: " + size); } return snapshot((int) size); } - public final ByteString snapshot(int byteCount) { + /** + * Returns an immutable copy of the first {@code byteCount} bytes of this buffer as a byte string. + */ + public ByteString snapshot(int byteCount) { if (byteCount == 0) return ByteString.EMPTY; return new ByteBuffer(this, byteCount); } - public final UnsafeCursor readUnsafe() { + public UnsafeCursor readUnsafe() { return readUnsafe(new UnsafeCursor()); } - public final UnsafeCursor readUnsafe(UnsafeCursor unsafeCursor) { + public UnsafeCursor readUnsafe(UnsafeCursor unsafeCursor) { if (null != unsafeCursor.buffer) { throw new IllegalStateException("already attached to a buffer"); } @@ -1927,11 +1996,11 @@ public final UnsafeCursor readUnsafe(UnsafeCursor unsafeCursor) { return unsafeCursor; } - public final UnsafeCursor readAndWriteUnsafe() { + public UnsafeCursor readAndWriteUnsafe() { return readAndWriteUnsafe(new UnsafeCursor()); } - public final UnsafeCursor readAndWriteUnsafe(UnsafeCursor unsafeCursor) { + public UnsafeCursor readAndWriteUnsafe(UnsafeCursor unsafeCursor) { if (null != unsafeCursor.buffer) { throw new IllegalStateException("already attached to a buffer"); } @@ -1954,13 +2023,23 @@ public static final class UnsafeCursor implements Closeable { public int end = -1; private Segment segment; - public final int next() { + /** + * Seeks to the next range of bytes, advancing the offset by {@code end - start}. Returns the + * size of the readable range (at least 1), or -1 if we have reached the end of the buffer and + * there are no more bytes to read. + */ + public int next() { if (offset == buffer.size) throw new IllegalStateException(); if (offset == -1L) return seek(0L); return seek(offset + (end - start)); } - public final int seek(long offset) { + /** + * Reposition the cursor so that the data at {@code offset} is readable at {@code data[start]}. + * Returns the number of bytes readable in {@code data} (at least 1), or -1 if there are no data + * to read. + */ + public int seek(long offset) { if (offset < -1 || offset > buffer.size) { throw new ArrayIndexOutOfBoundsException( String.format("offset=%s > size=%s", offset, buffer.size)); @@ -2013,7 +2092,7 @@ public final int seek(long offset) { } } - // If we're going to write and our segment is shared, swap it for a read-write first. + // If we're going to write and our segment is shared, swap it for a read-write one. if (readWrite && next.shared) { Segment unsharedNext = next.unsharedCopy(); if (buffer.head == next) { @@ -2023,6 +2102,7 @@ public final int seek(long offset) { next.prev.pop(); } + // Update this cursor to the requested offset within the found segment. this.segment = next; this.offset = offset; this.data = next.data; @@ -2031,8 +2111,25 @@ public final int seek(long offset) { return end - start; } - public final long resizeBuffer(long newSize) { - if (null == buffer) { + /** + * Change the size of the buffer so that it equals {@code newSize} by either adding new + * capacity at the end or truncating the buffer at the end. Newly added capacity may span + * multiple segments. + * + *

As a side-effect this cursor will {@link #seek seek}. If the buffer is being enlarged it + * will move {@link #offset} to the first byte of newly-added capacity. This is the size of the + * buffer prior to the {@code resizeBuffer()} call. If the buffer is being shrunk it will move + * {@link #offset} to the end of the buffer. + * + *

Warning: it is the caller’s responsibility to write new data to every byte of the + * newly-allocated capacity. Failure to do so may cause serious security problems as the data + * in the returned buffers is not zero filled. Buffers may contain dirty pooled segments that + * hold very sensitive data from other parts of the current process. + * + * @return the previous size of the buffer. + */ + public long resizeBuffer(long newSize) { + if (buffer == null) { throw new IllegalStateException("not attached to a buffer"); } if (!readWrite) { @@ -2089,6 +2186,29 @@ public final long resizeBuffer(long newSize) { return oldSize; } + /** + * Grow the buffer by adding a contiguous range of capacity in a single + * segment. This adds at least {@code minByteCount} bytes but may add up to a full segment of + * additional capacity. + * + *

As a side-effect this cursor will {@link #seek seek}. It will move {@link #offset} to the + * first byte of newly-added capacity. This is the size of the buffer prior to the {@code + * expandBuffer()} call. + * + *

If {@code minByteCount} bytes are available in the buffer's current tail segment that will + * be used; otherwise another segment will be allocated and appended. In either case this + * returns the number of bytes of capacity added to this buffer. + * + *

Warning: it is the caller’s responsibility to either write new data to every byte of the + * newly-allocated capacity, or to {@link #resizeBuffer shrink} the buffer to the data written. + * Failure to do so may cause serious security problems as the data in the returned buffers is + * not zero filled. Buffers may contain dirty pooled segments that hold very sensitive data from + * other parts of the current process. + * + * @param minByteCount the size of the contiguous capacity. Must be positive and not greater + * than the capacity size of a single segment (8 KiB). + * @return the number of bytes expanded by. Not less than {@code minByteCount}. + */ public final long expandBuffer(int minByteCount) { if (minByteCount <= 0) { throw new IllegalArgumentException("minByteCount <= 0: " + minByteCount); diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/buffer/ByteBuffer.java b/bus-core/src/main/java/org/aoju/bus/core/io/buffer/ByteBuffer.java old mode 100755 new mode 100644 index b0355da65f..8cf70e3ed5 --- a/bus-core/src/main/java/org/aoju/bus/core/io/buffer/ByteBuffer.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/buffer/ByteBuffer.java @@ -28,16 +28,11 @@ import org.aoju.bus.core.io.ByteString; import org.aoju.bus.core.io.Segment; import org.aoju.bus.core.toolkit.IoKit; -import org.aoju.bus.core.toolkit.ThreadKit; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; import java.util.Arrays; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; /** * 由字节数组段组成的不可变字节字符串 该类的存在是为了实现 @@ -49,25 +44,8 @@ */ public class ByteBuffer extends ByteString { - /** - * 守护线程在空闲时期回收内存资源 - */ - private static final ScheduledThreadPoolExecutor BUFFER_POOL_CLEAN = new ScheduledThreadPoolExecutor(1, r -> { - Thread thread = new Thread(r, "BufferPoolClean"); - thread.setDaemon(true); - return thread; - }); - /** - * 内存页游标 - */ - private final AtomicInteger cursor = new AtomicInteger(0); private transient byte[][] segments; private transient int[] directory; - /** - * 内存页组 - */ - private PageBuffer[] pageBuffers; - private boolean enabled = true; public ByteBuffer(Buffer buffer, int byteCount) { super(null); @@ -101,21 +79,6 @@ public ByteBuffer(Buffer buffer, int byteCount) { } } - /** - * @param pageSize 内存页大小 - * @param pageNo 内存页个数 - * @param isDirect 是否使用直接缓冲区 - */ - public ByteBuffer(final int pageSize, final int pageNo, final boolean isDirect) { - pageBuffers = new PageBuffer[pageNo]; - for (int i = 0; i < pageNo; i++) { - pageBuffers[i] = new PageBuffer(pageBuffers, pageSize, isDirect); - } - if (pageNo == 0 || pageSize == 0) { - future.cancel(false); - } - } - @Override public String utf8() { return toByteString().utf8(); @@ -151,28 +114,6 @@ public ByteString md5() { return toByteString().md5(); } - /** - * 内存回收任务 - */ - private final ScheduledFuture future = BUFFER_POOL_CLEAN.scheduleWithFixedDelay(new Runnable() { - @Override - public void run() { - if (enabled) { - for (PageBuffer pageBuffer : pageBuffers) { - pageBuffer.tryClean(); - } - } else { - if (null != pageBuffers) { - for (PageBuffer page : pageBuffers) { - page.release(); - } - pageBuffers = null; - } - future.cancel(false); - } - } - }, 500, 1000, TimeUnit.MILLISECONDS); - @Override public ByteString sha1() { return toByteString().sha1(); @@ -372,42 +313,4 @@ private Object writeReplace() { return toByteString(); } - /** - * 申请FastBufferThread的线程对象,配合线程池申请会有更好的性能表现 - * - * @param target Runnable - * @param name 线程名 - * @return FastBufferThread线程对象 - */ - public Thread newThread(Runnable target, String name) { - assertEnabled(); - ThreadKit.FastBufferThread thread = new ThreadKit.FastBufferThread(target, name); - thread.setPageIndex((int) (thread.getId() % pageBuffers.length)); - return thread; - } - - /** - * 申请内存页 - * - * @return 缓存页对象 - */ - public PageBuffer allocatePageBuffer() { - assertEnabled(); - return pageBuffers[(cursor.getAndIncrement() & Integer.MAX_VALUE) % pageBuffers.length]; - } - - private void assertEnabled() { - if (!enabled) { - throw new IllegalStateException("buffer pool is disable"); - } - } - - /** - * 释放回收内存 - */ - public void release() { - enabled = false; - } - - } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/buffer/CircularBuffer.java b/bus-core/src/main/java/org/aoju/bus/core/io/buffer/CircularBuffer.java new file mode 100755 index 0000000000..bf821f11e1 --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/io/buffer/CircularBuffer.java @@ -0,0 +1,239 @@ +package org.aoju.bus.core.io.buffer; + +import java.util.Objects; + +/** + * 循环缓冲区 + */ +public class CircularBuffer { + + private final byte[] buffer; + private int startOffset; + private int endOffset; + private int currentNumberOfBytes; + + /** + * 默认缓冲大小的构造 + */ + public CircularBuffer() { + this(2 << 12); + } + + /** + * 构造 + * + * @param pSize 缓冲大小 + */ + public CircularBuffer(final int pSize) { + buffer = new byte[pSize]; + startOffset = 0; + endOffset = 0; + currentNumberOfBytes = 0; + } + + /** + * 从buffer中读取下一个byte,同时移除这个bytes。 + * + * @return The byte + * @throws IllegalStateException buffer为空抛出,使用{@link #hasBytes()},或 {@link #getCurrentNumberOfBytes()}判断 + */ + public byte read() { + if (currentNumberOfBytes <= 0) { + throw new IllegalStateException("No bytes available."); + } + final byte b = buffer[startOffset]; + --currentNumberOfBytes; + if (++startOffset == buffer.length) { + startOffset = 0; + } + return b; + } + + /** + * Returns the given number of bytes from the buffer by storing them in + * the given byte array at the given offset. + * 从buffer中获取指定长度的bytes,从给定的targetBuffer的targetOffset位置写出 + * + * @param targetBuffer 目标bytes + * @param targetOffset 目标数组开始位置 + * @param length 读取长度 + * @throws NullPointerException 提供的数组为{@code null} + * @throws IllegalArgumentException {@code targetOffset}或{@code length} 为负数或{@code targetBuffer}太小 + * @throws IllegalStateException buffer中的byte不足,使用{@link #getCurrentNumberOfBytes()}判断。 + */ + public void read(final byte[] targetBuffer, final int targetOffset, final int length) { + Objects.requireNonNull(targetBuffer); + if (targetOffset < 0 || targetOffset >= targetBuffer.length) { + throw new IllegalArgumentException("Invalid offset: " + targetOffset); + } + if (length < 0 || length > buffer.length) { + throw new IllegalArgumentException("Invalid length: " + length); + } + if (targetOffset + length > targetBuffer.length) { + throw new IllegalArgumentException("The supplied byte array contains only " + + targetBuffer.length + " bytes, but offset, and length would require " + + (targetOffset + length - 1)); + } + if (currentNumberOfBytes < length) { + throw new IllegalStateException("Currently, there are only " + currentNumberOfBytes + + "in the buffer, not " + length); + } + int offset = targetOffset; + for (int i = 0; i < length; i++) { + targetBuffer[offset++] = buffer[startOffset]; + --currentNumberOfBytes; + if (++startOffset == buffer.length) { + startOffset = 0; + } + } + } + + /** + * 增加byte到buffer中 + * + * @param value The byte + * @throws IllegalStateException buffer已满. 用{@link #hasSpace()}或{@link #getSpace()}判断。 + */ + public void add(final byte value) { + if (currentNumberOfBytes >= buffer.length) { + throw new IllegalStateException("No space available"); + } + buffer[endOffset] = value; + ++currentNumberOfBytes; + if (++endOffset == buffer.length) { + endOffset = 0; + } + } + + /** + * Returns, whether the next bytes in the buffer are exactly those, given by + * {@code sourceBuffer}, {@code offset}, and {@code length}. No bytes are being + * removed from the buffer. If the result is true, then the following invocations + * of {@link #read()} are guaranteed to return exactly those bytes. + * + * @param sourceBuffer the buffer to compare against + * @param offset start offset + * @param length length to compare + * @return True, if the next invocations of {@link #read()} will return the + * bytes at offsets {@code pOffset}+0, {@code pOffset}+1, ..., + * {@code pOffset}+{@code pLength}-1 of byte array {@code pBuffer}. + * @throws IllegalArgumentException Either of {@code pOffset}, or {@code pLength} is negative. + * @throws NullPointerException The byte array {@code pBuffer} is null. + */ + public boolean peek(final byte[] sourceBuffer, final int offset, final int length) { + Objects.requireNonNull(sourceBuffer, "Buffer"); + if (offset < 0 || offset >= sourceBuffer.length) { + throw new IllegalArgumentException("Invalid offset: " + offset); + } + if (length < 0 || length > buffer.length) { + throw new IllegalArgumentException("Invalid length: " + length); + } + if (length < currentNumberOfBytes) { + return false; + } + int localOffset = startOffset; + for (int i = 0; i < length; i++) { + if (buffer[localOffset] != sourceBuffer[i + offset]) { + return false; + } + if (++localOffset == buffer.length) { + localOffset = 0; + } + } + return true; + } + + /** + * Adds the given bytes to the buffer. This is the same as invoking {@link #add(byte)} + * for the bytes at offsets {@code offset+0}, {@code offset+1}, ..., + * {@code offset+length-1} of byte array {@code targetBuffer}. + * + * @param targetBuffer the buffer to copy + * @param offset start offset + * @param length length to copy + * @throws IllegalStateException The buffer doesn't have sufficient space. Use + * {@link #getSpace()} to prevent this exception. + * @throws IllegalArgumentException Either of {@code pOffset}, or {@code pLength} is negative. + * @throws NullPointerException The byte array {@code pBuffer} is null. + */ + public void add(final byte[] targetBuffer, final int offset, final int length) { + Objects.requireNonNull(targetBuffer, "Buffer"); + if (offset < 0 || offset >= targetBuffer.length) { + throw new IllegalArgumentException("Invalid offset: " + offset); + } + if (length < 0) { + throw new IllegalArgumentException("Invalid length: " + length); + } + if (currentNumberOfBytes + length > buffer.length) { + throw new IllegalStateException("No space available"); + } + for (int i = 0; i < length; i++) { + buffer[endOffset] = targetBuffer[offset + i]; + if (++endOffset == buffer.length) { + endOffset = 0; + } + } + currentNumberOfBytes += length; + } + + /** + * Returns, whether there is currently room for a single byte in the buffer. + * Same as {@link #hasSpace(int) hasSpace(1)}. + * + * @return true if there is space for a byte + * @see #hasSpace(int) + * @see #getSpace() + */ + public boolean hasSpace() { + return currentNumberOfBytes < buffer.length; + } + + /** + * Returns, whether there is currently room for the given number of bytes in the buffer. + * + * @param count the byte count + * @return true if there is space for the given number of bytes + * @see #hasSpace() + * @see #getSpace() + */ + public boolean hasSpace(final int count) { + return currentNumberOfBytes + count <= buffer.length; + } + + /** + * Returns, whether the buffer is currently holding, at least, a single byte. + * + * @return true if the buffer is not empty + */ + public boolean hasBytes() { + return currentNumberOfBytes > 0; + } + + /** + * Returns the number of bytes, that can currently be added to the buffer. + * + * @return the number of bytes that can be added + */ + public int getSpace() { + return buffer.length - currentNumberOfBytes; + } + + /** + * Returns the number of bytes, that are currently present in the buffer. + * + * @return the number of bytes + */ + public int getCurrentNumberOfBytes() { + return currentNumberOfBytes; + } + + /** + * Removes all bytes from the buffer. + */ + public void clear() { + startOffset = 0; + endOffset = 0; + currentNumberOfBytes = 0; + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/copier/ChannelCopier.java b/bus-core/src/main/java/org/aoju/bus/core/io/copier/ChannelCopier.java index 83f22d8a09..14f542a5e2 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/io/copier/ChannelCopier.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/copier/ChannelCopier.java @@ -27,7 +27,6 @@ import org.aoju.bus.core.exception.InternalException; import org.aoju.bus.core.io.Progress; -import org.aoju.bus.core.lang.Assert; import org.aoju.bus.core.toolkit.IoKit; import java.io.IOException; @@ -84,9 +83,6 @@ public ChannelCopier(int bufferSize, long count, Progress progress) { @Override public long copy(ReadableByteChannel source, WritableByteChannel target) { - Assert.notNull(source, "InputStream is null !"); - Assert.notNull(target, "OutputStream is null !"); - final Progress progress = this.progress; if (null != progress) { progress.start(); @@ -132,7 +128,8 @@ private long doCopy(ReadableByteChannel source, WritableByteChannel target, Byte numToRead -= read; total += read; if (null != progress) { - progress.progress(this.count, total); + // 总长度未知的情况下,-1表示未知 + progress.progress(this.count < Long.MAX_VALUE ? this.count : -1, total); } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/copier/FileChannelCopier.java b/bus-core/src/main/java/org/aoju/bus/core/io/copier/FileChannelCopier.java new file mode 100755 index 0000000000..2495558a8c --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/io/copier/FileChannelCopier.java @@ -0,0 +1,121 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.io.copier; + +import org.aoju.bus.core.exception.InternalException; +import org.aoju.bus.core.toolkit.IoKit; + +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.FileChannel; + +/** + * {@link FileChannel} 数据拷贝封装 + * + * @author Kimi Liu + * @since Java 17+ + */ +public class FileChannelCopier extends IoCopier { + + /** + * 构造 + * + * @param count 拷贝总数,-1表示无限制 + */ + public FileChannelCopier(final long count) { + super(-1, count, null); + } + + /** + * 拷贝文件流,使用NIO + * + * @param in 输入 + * @param out 输出 + * @return 拷贝的字节数 + */ + public long copy(final FileInputStream in, final FileOutputStream out) { + FileChannel inChannel = null; + FileChannel outChannel = null; + try { + inChannel = in.getChannel(); + outChannel = out.getChannel(); + return copy(inChannel, outChannel); + } finally { + IoKit.close(outChannel); + IoKit.close(inChannel); + } + } + + @Override + public long copy(final FileChannel source, final FileChannel target) { + try { + return doCopySafely(source, target); + } catch (final IOException e) { + throw new InternalException(e); + } + } + + /** + * 文件拷贝实现 + * + *

+     * FileChannel#transferTo 或 FileChannel#transferFrom 的实现是平台相关的,需要确保低版本平台的兼容性
+     * 例如 android 7以下平台在使用 ZipInputStream 解压文件的过程中,
+     * 通过 FileChannel#transferFrom 传输到文件时,其返回值可能小于 totalBytes,不处理将导致文件内容缺失
+     *
+     * // 错误写法,dstChannel.transferFrom 返回值小于 zipEntry.getSize(),导致解压后文件内容缺失
+     * try (InputStream srcStream = zipFile.getInputStream(zipEntry);
+     * 		ReadableByteChannel srcChannel = Channels.newChannel(srcStream);
+     * 		FileOutputStream fos = new FileOutputStream(saveFile);
+     * 		FileChannel dstChannel = fos.getChannel()) {
+     * 		dstChannel.transferFrom(srcChannel, 0, zipEntry.getSize());
+     *  }
+     * 
+ * + * @param inChannel 输入通道 + * @param outChannel 输出通道 + * @return 输入通道的字节数 + * @throws IOException 发生IO错误 + */ + private long doCopySafely(final FileChannel inChannel, final FileChannel outChannel) throws IOException { + long totalBytes = inChannel.size(); + if (this.count > 0 && this.count < totalBytes) { + // 限制拷贝总数 + totalBytes = count; + } + // 确保文件内容不会缺失 + for (long pos = 0, remaining = totalBytes; remaining > 0; ) { + + // 实际传输的字节数 + final long writeBytes = inChannel.transferTo(pos, remaining, outChannel); + pos += writeBytes; + remaining -= writeBytes; + } + return totalBytes; + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/copier/StreamCopier.java b/bus-core/src/main/java/org/aoju/bus/core/io/copier/StreamCopier.java index b9f67b0fad..70fa977d35 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/io/copier/StreamCopier.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/copier/StreamCopier.java @@ -62,7 +62,7 @@ public StreamCopier(int bufferSize) { * 构造 * * @param bufferSize 缓存大小 - * @param count 拷贝总数 + * @param count 拷贝总数,-1表示无限制 */ public StreamCopier(int bufferSize, long count) { this(bufferSize, count, null); @@ -72,7 +72,7 @@ public StreamCopier(int bufferSize, long count) { * 构造 * * @param bufferSize 缓存大小 - * @param count 拷贝总数 + * @param count 拷贝总数,-1表示无限制 * @param progress 进度条 */ public StreamCopier(int bufferSize, long count, Progress progress) { diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/file/Tailer.java b/bus-core/src/main/java/org/aoju/bus/core/io/file/Tailer.java index b9645563de..ab0441e098 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/io/file/Tailer.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/file/Tailer.java @@ -121,13 +121,6 @@ public Tailer(final File file, final Charset charset, final XConsumer li this.executorService = Executors.newSingleThreadScheduledExecutor(); } - /** - * 开始监听 - */ - public void start() { - start(false); - } - /** * 检查文件有效性 * @@ -142,6 +135,13 @@ private static void checkFile(final File file) { } } + /** + * 开始监听 + */ + public void start() { + start(false); + } + /** * 结束,此方法需在异步模式或 */ diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/reader/LineReader.java b/bus-core/src/main/java/org/aoju/bus/core/io/reader/LineReader.java new file mode 100755 index 0000000000..3aec9cf140 --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/io/reader/LineReader.java @@ -0,0 +1,138 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.io.reader; + +import org.aoju.bus.core.collection.ComputeIterator; +import org.aoju.bus.core.lang.Symbol; +import org.aoju.bus.core.toolkit.CharsKit; +import org.aoju.bus.core.toolkit.IoKit; +import org.aoju.bus.core.toolkit.StringKit; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.nio.charset.Charset; +import java.util.Iterator; + +/** + * 行读取器,类似于BufferedInputStream,支持多行转义,规则如下: + *
    + *
  • 支持'\n'和'\r\n'两种换行符,不支持'\r'换行符
  • + *
  • 如果想读取转义符,必须定义为'\\'
  • + *
  • 多行转义后的换行符和空格都会被忽略
  • + *
+ * 读出后就是{@code a=12} + * + * @author Kimi Liu + * @since Java 17+ + */ +public class LineReader extends ReaderWrapper implements Iterable { + + /** + * 构造 + * + * @param in {@link InputStream} + * @param charset 编码 + */ + public LineReader(final InputStream in, final Charset charset) { + this(IoKit.getReader(in, charset)); + } + + /** + * 构造 + * + * @param reader {@link Reader} + */ + public LineReader(final Reader reader) { + super(IoKit.toBuffered(reader)); + } + + /** + * 读取一行 + * + * @return 内容 + * @throws IOException IO异常 + */ + public String readLine() throws IOException { + StringBuilder str = null; + // 换行符前是否为转义符 + boolean precedingBackslash = false; + int c; + while ((c = read()) > 0) { + if (null == str) { + // 只有有字符的情况下才初始化行,否则为行结束 + str = StringKit.builder(1024); + } + if (Symbol.C_BACKSLASH == c) { + // 转义符转义,行尾需要使用'\'时,使用转义符转义,即`\\` + if (false == precedingBackslash) { + // 转义符,添加标识,但是不加入字符 + precedingBackslash = true; + continue; + } else { + precedingBackslash = false; + } + } else { + if (precedingBackslash) { + // 转义模式下,跳过转义符后的所有空白符 + if (CharsKit.isBlankChar(c)) { + continue; + } + // 遇到普通字符,关闭转义 + precedingBackslash = false; + } else if (Symbol.C_LF == c) { + // 非转义状态下,表示行的结束 + // 如果换行符是`\r\n`,删除末尾的`\r` + final int lastIndex = str.length() - 1; + if (lastIndex >= 0 && Symbol.C_CR == str.charAt(lastIndex)) { + str.deleteCharAt(lastIndex); + } + break; + } + } + + str.append((char) c); + } + + return StringKit.toStringOrNull(str); + } + + @Override + public Iterator iterator() { + return new ComputeIterator<>() { + @Override + protected String computeNext() { + try { + return readLine(); + } catch (final IOException e) { + throw new RuntimeException(e); + } + } + }; + } + +} + diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/RandomFileInputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/reader/ReaderWrapper.java old mode 100644 new mode 100755 similarity index 72% rename from bus-core/src/main/java/org/aoju/bus/core/io/stream/RandomFileInputStream.java rename to bus-core/src/main/java/org/aoju/bus/core/io/reader/ReaderWrapper.java index daf8602cc5..6902e6a387 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/RandomFileInputStream.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/reader/ReaderWrapper.java @@ -23,57 +23,62 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.core.io.stream; +package org.aoju.bus.core.io.reader; + +import org.aoju.bus.core.lang.Assert; +import org.aoju.bus.core.lang.function.XWrapper; import java.io.IOException; -import java.io.InputStream; -import java.io.RandomAccessFile; +import java.io.Reader; +import java.nio.CharBuffer; /** + * {@link Reader} 包装 + * * @author Kimi Liu * @since Java 17+ */ -public class RandomFileInputStream extends InputStream { - - private final RandomAccessFile raf; +public class ReaderWrapper extends Reader implements XWrapper { - public RandomFileInputStream(RandomAccessFile raf) { - this.raf = raf; - } + protected final Reader raw; - @Override - public int read() throws IOException { - return raf.read(); + /** + * 构造 + * + * @param reader {@link Reader} + */ + public ReaderWrapper(final Reader reader) { + this.raw = Assert.notNull(reader); } @Override - public int read(byte[] b, int off, int len) throws IOException { - return raf.read(b, off, len); + public Reader getRaw() { + return this.raw; } @Override - public long skip(long n) throws IOException { - return raf.skipBytes((int) n); + public int read() throws IOException { + return raw.read(); } @Override - public void close() throws IOException { - raf.close(); + public int read(final CharBuffer target) throws IOException { + return raw.read(target); } @Override - public synchronized void reset() throws IOException { - raf.seek(0); + public int read(final char[] cbuf) throws IOException { + return raw.read(cbuf); } @Override - public int read(byte[] b) throws IOException { - return super.read(b); + public int read(final char[] buffer, final int off, final int len) throws IOException { + return raw.read(buffer, off, len); } @Override - public int available() throws IOException { - return (int) (raf.length() - raf.getFilePointer()); + public void close() throws IOException { + raw.close(); } -} \ No newline at end of file +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/resource/CharSequenceResource.java b/bus-core/src/main/java/org/aoju/bus/core/io/resource/CharSequenceResource.java index 3bb481d579..b667d687cd 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/io/resource/CharSequenceResource.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/resource/CharSequenceResource.java @@ -107,7 +107,7 @@ public String readString(Charset charset) throws InternalException { @Override public byte[] readBytes() throws InternalException { - return this.data.toString().getBytes(this.charset); + return StringKit.bytes(this.data, this.charset); } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/resource/MultiFileResource.java b/bus-core/src/main/java/org/aoju/bus/core/io/resource/MultiFileResource.java index 902c51908e..8b8b5eb732 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/io/resource/MultiFileResource.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/resource/MultiFileResource.java @@ -26,6 +26,7 @@ package org.aoju.bus.core.io.resource; import java.io.File; +import java.nio.file.Path; import java.util.Collection; /** @@ -40,28 +41,38 @@ public class MultiFileResource extends MultiResource { /** * 构造 * - * @param files 集合 + * @param file 文件 */ - public MultiFileResource(Collection files) { + public MultiFileResource(final File... file) { + add(file); + } + + /** + * 构造 + * + * @param files 文件资源列表 + */ + public MultiFileResource(final Path... files) { add(files); } /** * 构造 * - * @param file 文件 + * @param files 集合 */ - public MultiFileResource(File... file) { - add(file); + public MultiFileResource(final Collection files) { + add(files); } + /** * 增加文件资源 * * @param file 文件资源 * @return this */ - public MultiFileResource add(File... file) { + public MultiFileResource add(final File... file) { for (File f : file) { this.add(new FileResource(f)); } @@ -74,7 +85,20 @@ public MultiFileResource add(File... file) { * @param files 文件资源 * @return this */ - public MultiFileResource add(Collection files) { + public MultiFileResource add(final Path... files) { + for (final Path file : files) { + this.add(new FileResource(file)); + } + return this; + } + + /** + * 增加文件资源 + * + * @param files 文件资源 + * @return this + */ + public MultiFileResource add(final Collection files) { for (File file : files) { this.add(new FileResource(file)); } @@ -82,7 +106,7 @@ public MultiFileResource add(Collection files) { } @Override - public MultiFileResource add(Resource resource) { + public MultiFileResource add(final Resource resource) { return (MultiFileResource) super.add(resource); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/sink/AssignSink.java b/bus-core/src/main/java/org/aoju/bus/core/io/sink/AssignSink.java old mode 100755 new mode 100644 diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/sink/BufferSink.java b/bus-core/src/main/java/org/aoju/bus/core/io/sink/BufferSink.java old mode 100755 new mode 100644 index 9fd0eb3db5..670a911672 --- a/bus-core/src/main/java/org/aoju/bus/core/io/sink/BufferSink.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/sink/BufferSink.java @@ -43,54 +43,327 @@ */ public interface BufferSink extends Sink, WritableByteChannel { + /** + * Returns this sink's internal buffer. + */ Buffer buffer(); BufferSink write(ByteString byteString) throws IOException; + /** + * Like {@link OutputStream#write(byte[])}, this writes a complete byte array to + * this sink. + */ BufferSink write(byte[] source) throws IOException; + /** + * Like {@link OutputStream#write(byte[], int, int)}, this writes {@code byteCount} + * bytes of {@code source}, starting at {@code offset}. + */ BufferSink write(byte[] source, int offset, int byteCount) throws IOException; + /** + * Removes all bytes from {@code source} and appends them to this sink. Returns the + * number of bytes read which will be 0 if {@code source} is exhausted. + */ long writeAll(Source source) throws IOException; + /** + * Removes {@code byteCount} bytes from {@code source} and appends them to this sink. + */ BufferSink write(Source source, long byteCount) throws IOException; + /** + * Encodes {@code string} in UTF-8 and writes it to this sink.
{@code
+     *
+     *   Buffer buffer = new Buffer();
+     *   buffer.writeUtf8("Uh uh uh!");
+     *   buffer.writeByte(' ');
+     *   buffer.writeUtf8("You didn't say the magic word!");
+     *
+     *   assertEquals("Uh uh uh! You didn't say the magic word!", buffer.readUtf8());
+     * }
+ */ BufferSink writeUtf8(String string) throws IOException; + /** + * Encodes the characters at {@code beginIndex} up to {@code endIndex} from {@code string} in + * UTF-8 and writes it to this sink.
{@code
+     *
+     *   Buffer buffer = new Buffer();
+     *   buffer.writeUtf8("I'm a hacker!\n", 6, 12);
+     *   buffer.writeByte(' ');
+     *   buffer.writeUtf8("That's what I said: you're a nerd.\n", 29, 33);
+     *   buffer.writeByte(' ');
+     *   buffer.writeUtf8("I prefer to be called a hacker!\n", 24, 31);
+     *
+     *   assertEquals("hacker nerd hacker!", buffer.readUtf8());
+     * }
+ */ BufferSink writeUtf8(String string, int beginIndex, int endIndex) throws IOException; + /** + * Encodes {@code codePoint} in UTF-8 and writes it to this sink. + */ BufferSink writeUtf8CodePoint(int codePoint) throws IOException; + /** + * Encodes {@code string} in {@code charset} and writes it to this sink. + */ BufferSink writeString(String string, Charset charset) throws IOException; + /** + * Encodes the characters at {@code beginIndex} up to {@code endIndex} from {@code string} in + * {@code charset} and writes it to this sink. + */ BufferSink writeString(String string, int beginIndex, int endIndex, Charset charset) throws IOException; + /** + * Writes a byte to this sink. + */ BufferSink writeByte(int b) throws IOException; + /** + * Writes a big-endian short to this sink using two bytes.
{@code
+     *
+     *   Buffer buffer = new Buffer();
+     *   buffer.writeShort(32767);
+     *   buffer.writeShort(15);
+     *
+     *   assertEquals(4, buffer.size());
+     *   assertEquals((byte) 0x7f, buffer.readByte());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals((byte) 0x0f, buffer.readByte());
+     *   assertEquals(0, buffer.size());
+     * }
+ */ BufferSink writeShort(int s) throws IOException; + /** + * Writes a little-endian short to this sink using two bytes.
{@code
+     *
+     *   Buffer buffer = new Buffer();
+     *   buffer.writeShortLe(32767);
+     *   buffer.writeShortLe(15);
+     *
+     *   assertEquals(4, buffer.size());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0x7f, buffer.readByte());
+     *   assertEquals((byte) 0x0f, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals(0, buffer.size());
+     * }
+ */ BufferSink writeShortLe(int s) throws IOException; + /** + * Writes a big-endian int to this sink using four bytes.
{@code
+     *
+     *   Buffer buffer = new Buffer();
+     *   buffer.writeInt(2147483647);
+     *   buffer.writeInt(15);
+     *
+     *   assertEquals(8, buffer.size());
+     *   assertEquals((byte) 0x7f, buffer.readByte());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals((byte) 0x0f, buffer.readByte());
+     *   assertEquals(0, buffer.size());
+     * }
+ */ BufferSink writeInt(int i) throws IOException; + /** + * Writes a little-endian int to this sink using four bytes.
{@code
+     *
+     *   Buffer buffer = new Buffer();
+     *   buffer.writeIntLe(2147483647);
+     *   buffer.writeIntLe(15);
+     *
+     *   assertEquals(8, buffer.size());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0x7f, buffer.readByte());
+     *   assertEquals((byte) 0x0f, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals(0, buffer.size());
+     * }
+ */ BufferSink writeIntLe(int i) throws IOException; + /** + * Writes a big-endian long to this sink using eight bytes.
{@code
+     *
+     *   Buffer buffer = new Buffer();
+     *   buffer.writeLong(9223372036854775807L);
+     *   buffer.writeLong(15);
+     *
+     *   assertEquals(16, buffer.size());
+     *   assertEquals((byte) 0x7f, buffer.readByte());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals((byte) 0x0f, buffer.readByte());
+     *   assertEquals(0, buffer.size());
+     * }
+ */ BufferSink writeLong(long v) throws IOException; + /** + * Writes a little-endian long to this sink using eight bytes.
{@code
+     *
+     *   Buffer buffer = new Buffer();
+     *   buffer.writeLongLe(9223372036854775807L);
+     *   buffer.writeLongLe(15);
+     *
+     *   assertEquals(16, buffer.size());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0xff, buffer.readByte());
+     *   assertEquals((byte) 0x7f, buffer.readByte());
+     *   assertEquals((byte) 0x0f, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals((byte) 0x00, buffer.readByte());
+     *   assertEquals(0, buffer.size());
+     * }
+ */ BufferSink writeLongLe(long v) throws IOException; + /** + * Writes a long to this sink in signed decimal form (i.e., as a string in base 10).
{@code
+     *
+     *   Buffer buffer = new Buffer();
+     *   buffer.writeDecimalLong(8675309L);
+     *   buffer.writeByte(' ');
+     *   buffer.writeDecimalLong(-123L);
+     *   buffer.writeByte(' ');
+     *   buffer.writeDecimalLong(1L);
+     *
+     *   assertEquals("8675309 -123 1", buffer.readUtf8());
+     * }
+ */ BufferSink writeDecimalLong(long v) throws IOException; + /** + * Writes a long to this sink in hexadecimal form (i.e., as a string in base 16).
{@code
+     *
+     *   Buffer buffer = new Buffer();
+     *   buffer.writeHexadecimalUnsignedLong(65535L);
+     *   buffer.writeByte(' ');
+     *   buffer.writeHexadecimalUnsignedLong(0xcafebabeL);
+     *   buffer.writeByte(' ');
+     *   buffer.writeHexadecimalUnsignedLong(0x10L);
+     *
+     *   assertEquals("ffff cafebabe 10", buffer.readUtf8());
+     * }
+ */ BufferSink writeHexadecimalUnsignedLong(long v) throws IOException; + /** + * Writes all buffered data to the underlying sink, if one exists. Then that sink is recursively + * flushed which pushes data as far as possible towards its ultimate destination. Typically that + * destination is a network socket or file.
{@code
+     *
+     *   BufferedSink b0 = new Buffer();
+     *   BufferedSink b1 = Okio.buffer(b0);
+     *   BufferedSink b2 = Okio.buffer(b1);
+     *
+     *   b2.writeUtf8("hello");
+     *   assertEquals(5, b2.buffer().size());
+     *   assertEquals(0, b1.buffer().size());
+     *   assertEquals(0, b0.buffer().size());
+     *
+     *   b2.flush();
+     *   assertEquals(0, b2.buffer().size());
+     *   assertEquals(0, b1.buffer().size());
+     *   assertEquals(5, b0.buffer().size());
+     * }
+ */ @Override void flush() throws IOException; + /** + * Writes all buffered data to the underlying sink, if one exists. Like {@link #flush}, but + * weaker. Call this before this buffered sink goes out of scope so that its data can reach its + * destination.
{@code
+     *
+     *   BufferedSink b0 = new Buffer();
+     *   BufferedSink b1 = Okio.buffer(b0);
+     *   BufferedSink b2 = Okio.buffer(b1);
+     *
+     *   b2.writeUtf8("hello");
+     *   assertEquals(5, b2.buffer().size());
+     *   assertEquals(0, b1.buffer().size());
+     *   assertEquals(0, b0.buffer().size());
+     *
+     *   b2.emit();
+     *   assertEquals(0, b2.buffer().size());
+     *   assertEquals(5, b1.buffer().size());
+     *   assertEquals(0, b0.buffer().size());
+     *
+     *   b1.emit();
+     *   assertEquals(0, b2.buffer().size());
+     *   assertEquals(0, b1.buffer().size());
+     *   assertEquals(5, b0.buffer().size());
+     * }
+ */ BufferSink emit() throws IOException; + /** + * Writes complete segments to the underlying sink, if one exists. Like {@link #flush}, but + * weaker. Use this to limit the memory held in the buffer to a single segment. Typically + * application code will not need to call this: it is only necessary when application code writes + * directly to this {@linkplain #buffer() sink's buffer}.
{@code
+     *
+     *   BufferedSink b0 = new Buffer();
+     *   BufferedSink b1 = Okio.buffer(b0);
+     *   BufferedSink b2 = Okio.buffer(b1);
+     *
+     *   b2.buffer().write(new byte[20_000]);
+     *   assertEquals(20_000, b2.buffer().size());
+     *   assertEquals(     0, b1.buffer().size());
+     *   assertEquals(     0, b0.buffer().size());
+     *
+     *   b2.emitCompleteSegments();
+     *   assertEquals( 3_616, b2.buffer().size());
+     *   assertEquals(     0, b1.buffer().size());
+     *   assertEquals(16_384, b0.buffer().size()); // This example assumes 8192 byte segments.
+     * }
+ */ BufferSink emitCompleteSegments() throws IOException; + /** + * Returns an output stream that writes to this sink. + */ OutputStream outputStream(); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/sink/DeflaterSink.java b/bus-core/src/main/java/org/aoju/bus/core/io/sink/DeflaterSink.java index 484b37d6d9..38db6fdd96 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/io/sink/DeflaterSink.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/sink/DeflaterSink.java @@ -51,13 +51,14 @@ public DeflaterSink(Sink sink, Deflater deflater) { this(IoKit.buffer(sink), deflater); } + /** + * This package-private constructor shares a buffer with its trusted caller. + * In general we can't share a BufferedSource because the deflater holds input + * bytes until they are inflated. + */ DeflaterSink(BufferSink sink, Deflater deflater) { - if (null == sink) { - throw new IllegalArgumentException("source == null"); - } - if (null == deflater) { - throw new IllegalArgumentException("inflater == null"); - } + if (sink == null) throw new IllegalArgumentException("source == null"); + if (deflater == null) throw new IllegalArgumentException("inflater == null"); this.sink = sink; this.deflater = deflater; } @@ -66,12 +67,15 @@ public DeflaterSink(Sink sink, Deflater deflater) { public void write(Buffer source, long byteCount) throws IOException { IoKit.checkOffsetAndCount(source.size, 0, byteCount); while (byteCount > 0) { + // Share bytes from the head segment of 'source' with the deflater. Segment head = source.head; int toDeflate = (int) Math.min(byteCount, head.limit - head.pos); deflater.setInput(head.data, head.pos, toDeflate); + // Deflate those bytes into sink. deflate(false); + // Mark those bytes as read. source.size -= toDeflate; head.pos += toDeflate; if (head.pos == head.limit) { @@ -88,6 +92,10 @@ private void deflate(boolean syncFlush) throws IOException { while (true) { Segment s = buffer.writableSegment(1); + // The 4-parameter overload of deflate() doesn't exist in the RI until + // Java 1.7, and is public (although with @hide) on Android since 2.3. + // The @hide tag means that this code won't compile against the Android + // 2.3 SDK, but it will run fine there. int deflated = syncFlush ? deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit, Deflater.SYNC_FLUSH) : deflater.deflate(s.data, s.limit, Segment.SIZE - s.limit); @@ -98,6 +106,7 @@ private void deflate(boolean syncFlush) throws IOException { sink.emitCompleteSegments(); } else if (deflater.needsInput()) { if (s.pos == s.limit) { + // We allocated a tail segment, but didn't end up needing it. Recycle! buffer.head = s.pop(); LifeCycle.recycle(s); } @@ -118,9 +127,11 @@ void finishDeflate() throws IOException { } @Override - public void close() { + public void close() throws IOException { if (closed) return; + // Emit deflated data to the underlying sink. If this fails, we still need + // to close the deflater and the sink; otherwise we risk leaking resources. Throwable thrown = null; try { finishDeflate(); @@ -131,21 +142,17 @@ public void close() { try { deflater.end(); } catch (Throwable e) { - if (null == thrown) { - thrown = e; - } + if (thrown == null) thrown = e; } try { sink.close(); } catch (Throwable e) { - if (null == thrown) { - thrown = e; - } + if (thrown == null) thrown = e; } closed = true; - if (null != thrown) IoKit.sneakyRethrow(thrown); + if (thrown != null) IoKit.sneakyRethrow(thrown); } @Override diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/sink/GzipSink.java b/bus-core/src/main/java/org/aoju/bus/core/io/sink/GzipSink.java index 577374b94c..b332f61340 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/io/sink/GzipSink.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/sink/GzipSink.java @@ -42,14 +42,26 @@ * @author Kimi Liu * @since Java 17+ */ -public final class GzipSink implements Sink { +public class GzipSink implements Sink { + /** + * Sink into which the GZIP format is written. + */ private final BufferSink sink; + /** + * The deflater used to compress the body. + */ private final Deflater deflater; + /** + * The deflater sink takes care of moving data between decompressed source and + * compressed sink buffers. + */ private final DeflaterSink deflaterSink; - + /** + * Checksum calculated for the compressed body. + */ private final CRC32 crc = new CRC32(); private boolean closed; @@ -84,9 +96,14 @@ public Timeout timeout() { } @Override - public void close() { + public void close() throws IOException { if (closed) return; + // This method delegates to the DeflaterSink for finishing the deflate process + // but keeps responsibility for releasing the deflater's resources. This is + // necessary because writeFooter needs to query the processed byte count which + // only works when the deflater is still open. + Throwable thrown = null; try { deflaterSink.finishDeflate(); @@ -98,42 +115,48 @@ public void close() { try { deflater.end(); } catch (Throwable e) { - if (null == thrown) { - thrown = e; - } + if (thrown == null) thrown = e; } try { sink.close(); } catch (Throwable e) { - if (null == thrown) { - thrown = e; - } + if (thrown == null) thrown = e; } closed = true; - if (null != thrown) IoKit.sneakyRethrow(thrown); + if (thrown != null) { + IoKit.sneakyRethrow(thrown); + } } + /** + * Returns the {@link Deflater}. + * Use it to access stats, dictionary, compression level, etc. + */ public final Deflater deflater() { return deflater; } private void writeHeader() { + // Write the Gzip header directly into the buffer for the sink to avoid handling IOException. Buffer buffer = this.sink.buffer(); - buffer.writeShort(0x1f8b); - buffer.writeByte(0x08); - buffer.writeByte(0x00); - buffer.writeInt(0x00); - buffer.writeByte(0x00); - buffer.writeByte(0x00); + buffer.writeShort(0x1f8b); // Two-byte Gzip ID. + buffer.writeByte(0x08); // 8 == Deflate compression method. + buffer.writeByte(0x00); // No flags. + buffer.writeInt(0x00); // No modification time. + buffer.writeByte(0x00); // No extra flags. + buffer.writeByte(0x00); // No OS. } private void writeFooter() throws IOException { - sink.writeIntLe((int) crc.getValue()); - sink.writeIntLe((int) deflater.getBytesRead()); + sink.writeIntLe((int) crc.getValue()); // CRC of original data. + sink.writeIntLe((int) deflater.getBytesRead()); // Length of original data. } + /** + * Updates the CRC with the given bytes. + */ private void updateCrc(Buffer buffer, long byteCount) { for (Segment head = buffer.head; byteCount > 0; head = head.next) { int segmentLength = (int) Math.min(byteCount, head.limit - head.pos); diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/sink/RealSink.java b/bus-core/src/main/java/org/aoju/bus/core/io/sink/RealSink.java old mode 100755 new mode 100644 diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/sink/Sink.java b/bus-core/src/main/java/org/aoju/bus/core/io/sink/Sink.java old mode 100755 new mode 100644 index f5a3e9a703..160f60b31b --- a/bus-core/src/main/java/org/aoju/bus/core/io/sink/Sink.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/sink/Sink.java @@ -43,14 +43,28 @@ */ public interface Sink extends Closeable, Flushable { - @Override - void flush() throws IOException; + /** + * Removes {@code byteCount} bytes from {@code source} and appends them to this. + */ + void write(Buffer source, long byteCount) throws IOException; + /** + * Pushes all buffered bytes to their final destination. + */ @Override - void close() throws IOException; + void flush() throws IOException; + /** + * Returns the timeout for this sink. + */ Timeout timeout(); - void write(Buffer source, long byteCount) throws IOException; + /** + * Pushes all buffered bytes to their final destination and releases the + * resources held by this sink. It is an error to write a closed sink. It is + * safe to close a sink more than once. + */ + @Override + void close() throws IOException; } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/source/AssignSource.java b/bus-core/src/main/java/org/aoju/bus/core/io/source/AssignSource.java old mode 100755 new mode 100644 index 0f7f4fb1b8..467d3332ad --- a/bus-core/src/main/java/org/aoju/bus/core/io/source/AssignSource.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/source/AssignSource.java @@ -69,7 +69,7 @@ public void close() throws IOException { @Override public String toString() { - return getClass().getSimpleName() + Symbol.PARENTHESE_LEFT + delegate.toString() + Symbol.PARENTHESE_RIGHT; + return getClass().getSimpleName() + Symbol.PARENTHESE_LEFT + delegate + Symbol.PARENTHESE_RIGHT; } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/source/BufferSource.java b/bus-core/src/main/java/org/aoju/bus/core/io/source/BufferSource.java old mode 100755 new mode 100644 index 85b734ef95..9e6d01ecaa --- a/bus-core/src/main/java/org/aoju/bus/core/io/source/BufferSource.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/source/BufferSource.java @@ -45,47 +45,35 @@ public interface BufferSource extends Source, ReadableByteChannel { /** - * @return this source's internal buffer. - * use getBuffer() instead. - */ - Buffer buffer(); - - /** - * @return This source's internal buffer. + * This source's internal buffer. */ Buffer getBuffer(); /** - * @return true if there are no more bytes in this source. This will block until there are bytes + * Returns true if there are no more bytes in this source. This will block until there are bytes * to read or the source is definitely exhausted. - * @throws IOException {@link java.io.IOException} IOException. */ boolean exhausted() throws IOException; /** - * when the buffer contains at least {@code byteCount} bytes. - * - * @param byteCount long - * @throws IOException {@link java.io.IOException} IOException. + * Returns when the buffer contains at least {@code byteCount} bytes. Throws an + * {@link java.io.EOFException} if the source is exhausted before the required bytes can be read. */ void require(long byteCount) throws IOException; /** - * @param byteCount long - * @return true when the buffer contains at least {@code byteCount} bytes, expanding it as + * Returns true when the buffer contains at least {@code byteCount} bytes, expanding it as * necessary. Returns false if the source is exhausted before the requested bytes can be read. - * @throws IOException {@link java.io.IOException} IOException. */ boolean request(long byteCount) throws IOException; /** - * @return Removes a byte from this source and returns it. - * @throws IOException {@link java.io.IOException} IOException. + * Removes a byte from this source and returns it. */ byte readByte() throws IOException; /** - * @return two bytes from this source and returns a big-endian short.
{@code
+     * Removes two bytes from this source and returns a big-endian short. 
{@code
      *
      *   Buffer buffer = new Buffer()
      *       .writeByte(0x7f)
@@ -100,7 +88,6 @@ public interface BufferSource extends Source, ReadableByteChannel {
      *   assertEquals(15, buffer.readShort());
      *   assertEquals(0, buffer.size());
      * }
- * @throws IOException {@link java.io.IOException} IOException. */ short readShort() throws IOException; @@ -120,27 +107,83 @@ public interface BufferSource extends Source, ReadableByteChannel { * assertEquals(15, buffer.readShortLe()); * assertEquals(0, buffer.size()); * }
- * - * @return the short - * @throws IOException {@link java.io.IOException} IOException. */ short readShortLe() throws IOException; /** - * @return the int - * @throws IOException {@link java.io.IOException} IOException. + * Removes four bytes from this source and returns a big-endian int.
{@code
+     *
+     *   Buffer buffer = new Buffer()
+     *       .writeByte(0x7f)
+     *       .writeByte(0xff)
+     *       .writeByte(0xff)
+     *       .writeByte(0xff)
+     *       .writeByte(0x00)
+     *       .writeByte(0x00)
+     *       .writeByte(0x00)
+     *       .writeByte(0x0f);
+     *   assertEquals(8, buffer.size());
+     *
+     *   assertEquals(2147483647, buffer.readInt());
+     *   assertEquals(4, buffer.size());
+     *
+     *   assertEquals(15, buffer.readInt());
+     *   assertEquals(0, buffer.size());
+     * }
*/ int readInt() throws IOException; /** - * @return the int - * @throws IOException {@link java.io.IOException} IOException. + * Removes four bytes from this source and returns a little-endian int.
{@code
+     *
+     *   Buffer buffer = new Buffer()
+     *       .writeByte(0xff)
+     *       .writeByte(0xff)
+     *       .writeByte(0xff)
+     *       .writeByte(0x7f)
+     *       .writeByte(0x0f)
+     *       .writeByte(0x00)
+     *       .writeByte(0x00)
+     *       .writeByte(0x00);
+     *   assertEquals(8, buffer.size());
+     *
+     *   assertEquals(2147483647, buffer.readIntLe());
+     *   assertEquals(4, buffer.size());
+     *
+     *   assertEquals(15, buffer.readIntLe());
+     *   assertEquals(0, buffer.size());
+     * }
*/ int readIntLe() throws IOException; /** - * @return the long - * @throws IOException {@link java.io.IOException} IOException. + * Removes eight bytes from this source and returns a big-endian long.
{@code
+     *
+     *   Buffer buffer = new Buffer()
+     *       .writeByte(0x7f)
+     *       .writeByte(0xff)
+     *       .writeByte(0xff)
+     *       .writeByte(0xff)
+     *       .writeByte(0xff)
+     *       .writeByte(0xff)
+     *       .writeByte(0xff)
+     *       .writeByte(0xff)
+     *       .writeByte(0x00)
+     *       .writeByte(0x00)
+     *       .writeByte(0x00)
+     *       .writeByte(0x00)
+     *       .writeByte(0x00)
+     *       .writeByte(0x00)
+     *       .writeByte(0x00)
+     *       .writeByte(0x0f);
+     *   assertEquals(16, buffer.size());
+     *
+     *   assertEquals(9223372036854775807L, buffer.readLong());
+     *   assertEquals(8, buffer.size());
+     *
+     *   assertEquals(15, buffer.readLong());
+     *   assertEquals(0, buffer.size());
+     * }
*/ long readLong() throws IOException; @@ -172,9 +215,6 @@ public interface BufferSource extends Source, ReadableByteChannel { * assertEquals(15, buffer.readLongLe()); * assertEquals(0, buffer.size()); * }
- * - * @return the long - * @throws IOException {@link java.io.IOException} IOException. */ long readLongLe() throws IOException; @@ -192,9 +232,8 @@ public interface BufferSource extends Source, ReadableByteChannel { * assertEquals(1L, buffer.readDecimalLong()); * } * - * @return the long - * @throws IOException if the found digits do not fit into a {@code long} or a decimal - * number was not present. + * @throws NumberFormatException if the found digits do not fit into a {@code long} or a decimal + * number was not present. */ long readDecimalLong() throws IOException; @@ -212,36 +251,25 @@ public interface BufferSource extends Source, ReadableByteChannel { * assertEquals(0x10L, buffer.readHexadecimalUnsignedLong()); * } * - * @return the long - * @throws IOException if the found hexadecimal does not fit into a {@code long} or - * hexadecimal was not found. + * @throws NumberFormatException if the found hexadecimal does not fit into a {@code long} or + * hexadecimal was not found. */ long readHexadecimalUnsignedLong() throws IOException; /** * Reads and discards {@code byteCount} bytes from this source. Throws an - * {@link java.io.IOException} if the source is exhausted before the + * {@link java.io.EOFException} if the source is exhausted before the * requested bytes can be skipped. - * - * @param byteCount long - * @throws IOException {@link java.io.IOException} IOException. */ void skip(long byteCount) throws IOException; /** * Removes all bytes bytes from this and returns them as a byte string. - * - * @return the ByteString - * @throws IOException {@link java.io.IOException} IOException. */ ByteString readByteString() throws IOException; /** * Removes {@code byteCount} bytes from this and returns them as a byte string. - * - * @param byteCount long - * @return the ByteString - * @throws IOException {@link java.io.IOException} IOException. */ ByteString readByteString(long byteCount) throws IOException; @@ -269,78 +297,46 @@ public interface BufferSource extends Source, ReadableByteChannel { * assertEquals(480, buffer.readDecimalLong()); * assertEquals('\n', buffer.readByte()); * } - * - * @param options Options - * @return the int - * @throws IOException {@link java.io.IOException} IOException. */ - int select(Blending options) throws IOException; + int select(Blending blending) throws IOException; /** * Removes all bytes from this and returns them as a byte array. - * - * @return the byte[] - * @throws IOException {@link java.io.IOException} IOException. */ byte[] readByteArray() throws IOException; /** * Removes {@code byteCount} bytes from this and returns them as a byte array. - * - * @param byteCount long - * @return the byte[] - * @throws IOException {@link java.io.IOException} IOException. */ byte[] readByteArray(long byteCount) throws IOException; /** * Removes up to {@code sink.length} bytes from this and copies them into {@code sink}. Returns * the number of bytes read, or -1 if this source is exhausted. - * - * @param sink byte[] - * @return the long - * @throws IOException {@link java.io.IOException} IOException. */ int read(byte[] sink) throws IOException; /** * Removes exactly {@code sink.length} bytes from this and copies them into {@code sink}. Throws - * an {@link java.io.IOException} if the requested number of bytes cannot be read. - * - * @param sink byte[] - * @throws IOException {@link java.io.IOException} IOException. + * an {@link java.io.EOFException} if the requested number of bytes cannot be read. */ void readFully(byte[] sink) throws IOException; /** * Removes up to {@code byteCount} bytes from this and copies them into {@code sink} at {@code * offset}. Returns the number of bytes read, or -1 if this source is exhausted. - * - * @param sink byte[] - * @param offset int - * @param byteCount int - * @return the int - * @throws IOException {@link java.io.IOException} IOException. */ int read(byte[] sink, int offset, int byteCount) throws IOException; /** * Removes exactly {@code byteCount} bytes from this and appends them to {@code sink}. Throws an - * {@link java.io.IOException} if the requested number of bytes cannot be read. - * - * @param sink Buffer - * @param byteCount long - * @throws IOException {@link java.io.IOException} IOException. + * {@link java.io.EOFException} if the requested number of bytes cannot be read. */ void readFully(Buffer sink, long byteCount) throws IOException; /** * Removes all bytes from this and appends them to {@code sink}. Returns the total number of bytes * written to {@code sink} which will be 0 if this is exhausted. - * - * @param sink Sink - * @return the long - * @throws IOException {@link java.io.IOException} IOException. */ long readAll(Sink sink) throws IOException; @@ -359,9 +355,6 @@ public interface BufferSource extends Source, ReadableByteChannel { * assertEquals("", buffer.readUtf8()); * assertEquals(0, buffer.size()); * } - * - * @return the String - * @throws IOException {@link java.io.IOException} IOException. */ String readUtf8() throws IOException; @@ -384,10 +377,6 @@ public interface BufferSource extends Source, ReadableByteChannel { * assertEquals(" magic word!", buffer.readUtf8(12)); * assertEquals(0, buffer.size()); * } - * - * @param byteCount long - * @return the String - * @throws IOException {@link java.io.IOException} IOException. */ String readUtf8(long byteCount) throws IOException; @@ -419,9 +408,6 @@ public interface BufferSource extends Source, ReadableByteChannel { * java.io.BufferedReader}. If the source doesn't end with a line break then an implicit line * break is assumed. Null is returned once the source is exhausted. Use this for human-generated * data, where a trailing line break is optional. - * - * @return the String - * @throws IOException {@link java.io.IOException} IOException. */ String readUtf8Line() throws IOException; @@ -430,12 +416,9 @@ public interface BufferSource extends Source, ReadableByteChannel { * either {@code "\n"} or {@code "\r\n"}; these characters are not included in the result. * *

On the end of the stream this method throws. Every call must consume either - * '\r\n' or '\n'. If these characters are absent in the stream, an {@link java.io.IOException} + * '\r\n' or '\n'. If these characters are absent in the stream, an {@link java.io.EOFException} * is thrown. Use this for machine-generated data where a missing line break implies truncated * input. - * - * @return the String - * @throws IOException {@link java.io.IOException} IOException. */ String readUtf8LineStrict() throws IOException; @@ -446,7 +429,7 @@ public interface BufferSource extends Source, ReadableByteChannel { * *

The returned string will have at most {@code limit} UTF-8 bytes, and the maximum number * of bytes scanned is {@code limit + 2}. If {@code limit == 0} this will always throw - * an {@code IOException} because no bytes will be scanned. + * an {@code EOFException} because no bytes will be scanned. * *

This method is safe. No bytes are discarded if the match fails, and the caller is free * to try another match:

{@code
@@ -460,53 +443,38 @@ public interface BufferSource extends Source, ReadableByteChannel {
      *   // No bytes have been consumed so the caller can retry.
      *   assertEquals("12345", buffer.readUtf8LineStrict(5));
      * }
- * - * @param limit long - * @return String String - * @throws IOException {@link java.io.IOException} IOException. */ String readUtf8LineStrict(long limit) throws IOException; /** - * @return Removes and returns a single UTF-8 code point, reading between 1 and 4 bytes as necessary. + * Removes and returns a single UTF-8 code point, reading between 1 and 4 bytes as necessary. * *

If this source is exhausted before a complete code point can be read, this throws an {@link - * java.io.IOException} and consumes no input. + * java.io.EOFException} and consumes no input. * *

If this source doesn't start with a properly-encoded UTF-8 code point, this method will * remove 1 or more non-UTF-8 bytes and return the replacement character ({@code U+FFFD}). This * covers encoding problems (the input is not properly-encoded UTF-8), characters out of range * (beyond the 0x10ffff limit of Unicode), code points for UTF-16 surrogates (U+d800..U+dfff) and * overlong encodings (such as {@code 0xc080} for the NUL character in modified UTF-8). - * @throws IOException {@link java.io.IOException} IOException. */ int readUtf8CodePoint() throws IOException; /** - * @param charset Charset Removes all bytes from this, decodes them as {@code charset}, - * @return the string. - * @throws IOException {@link java.io.IOException} IOException. + * Removes all bytes from this, decodes them as {@code charset}, and returns the string. */ String readString(Charset charset) throws IOException; /** - * Removes {@code byteCount} bytes from this, decodes them as {@code charset}, - * - * @param byteCount byteCount - * @param charset Charset - * @return the string. - * @throws IOException {@link java.io.IOException} IOException. + * Removes {@code byteCount} bytes from this, decodes them as {@code charset}, and returns the + * string. */ String readString(long byteCount, Charset charset) throws IOException; /** * Equivalent to {@link #indexOf(byte, long) indexOf(b, 0)}. - * - * @param bytes byte - * @return the long - * @throws IOException {@link java.io.IOException} IOException. */ - long indexOf(byte bytes) throws IOException; + long indexOf(byte b) throws IOException; /** * Returns the index of the first {@code b} in the buffer at or after {@code fromIndex}. This @@ -521,13 +489,8 @@ public interface BufferSource extends Source, ReadableByteChannel { * assertEquals(6, buffer.indexOf(m)); * assertEquals(40, buffer.indexOf(m, 12)); * } - * - * @param bytes byte - * @param fromIndex long - * @return the long - * @throws IOException {@link java.io.IOException} IOException. */ - long indexOf(byte bytes, long fromIndex) throws IOException; + long indexOf(byte b, long fromIndex) throws IOException; /** * Returns the index of {@code b} if it is found in the range of {@code fromIndex} inclusive @@ -536,21 +499,11 @@ public interface BufferSource extends Source, ReadableByteChannel { * *

The scan terminates at either {@code toIndex} or the end of the buffer, whichever comes * first. The maximum number of bytes scanned is {@code toIndex-fromIndex}. - * - * @param bytes byte - * @param fromIndex long - * @param toIndex long - * @return the long - * @throws IOException {@link java.io.IOException} IOException. */ - long indexOf(byte bytes, long fromIndex, long toIndex) throws IOException; + long indexOf(byte b, long fromIndex, long toIndex) throws IOException; /** * Equivalent to {@link #indexOf(ByteString, long) indexOf(bytes, 0)}. - * - * @param bytes ByteString - * @return the long - * @throws IOException {@link java.io.IOException} IOException. */ long indexOf(ByteString bytes) throws IOException; @@ -568,68 +521,57 @@ public interface BufferSource extends Source, ReadableByteChannel { * assertEquals(6, buffer.indexOf(MOVE)); * assertEquals(40, buffer.indexOf(MOVE, 12)); * } - * - * @param bytes ByteString - * @param fromIndex long - * @return the long - * @throws IOException {@link java.io.IOException} IOException. */ long indexOf(ByteString bytes, long fromIndex) throws IOException; /** - * @param targetBytes ByteString - * Equivalent to {@link #indexOfElement(ByteString, long) indexOfElement(targetBytes, 0)}. - * @return the long - * @throws IOException {@link java.io.IOException} IOException. + * Equivalent to {@link #indexOfElement(ByteString, long) indexOfElement(targetBytes, 0)}. */ long indexOfElement(ByteString targetBytes) throws IOException; /** - * @param fromIndex long - * @param bytes ByteString - * @return the long - * @throws IOException {@link java.io.IOException} IOException. + * Returns the first index in this buffer that is at or after {@code fromIndex} and that contains + * any of the bytes in {@code targetBytes}. This expands the buffer as necessary until a target + * byte is found. This reads an unbounded number of bytes into the buffer. Returns -1 if the + * stream is exhausted before the requested byte is found.

{@code
+     *
+     *   ByteString ANY_VOWEL = ByteString.encodeUtf8("AEOIUaeoiu");
+     *
+     *   Buffer buffer = new Buffer();
+     *   buffer.writeUtf8("Dr. Alan Grant");
+     *
+     *   assertEquals(4,  buffer.indexOfElement(ANY_VOWEL));    // 'A' in 'Alan'.
+     *   assertEquals(11, buffer.indexOfElement(ANY_VOWEL, 9)); // 'a' in 'Grant'.
+     * }
*/ - long indexOfElement(ByteString bytes, long fromIndex) throws IOException; + long indexOfElement(ByteString targetBytes, long fromIndex) throws IOException; /** - * true if the bytes at {@code offset} in this source equal {@code bytes}. This expands + * Returns true if the bytes at {@code offset} in this source equal {@code bytes}. This expands * the buffer as necessary until a byte does not match, all bytes are matched, or if the stream * is exhausted before enough bytes could determine a match.
{@code
      *
-     *                 ByteString simonSays = ByteString.encodeUtf8("Simon says:");
+     *   ByteString simonSays = ByteString.encodeUtf8("Simon says:");
      *
-     *                 Buffer standOnOneLeg = new Buffer().writeUtf8("Simon says: Stand on first leg.");
-     *                 assertTrue(standOnOneLeg.rangeEquals(0, simonSays));
+     *   Buffer standOnOneLeg = new Buffer().writeUtf8("Simon says: Stand on one leg.");
+     *   assertTrue(standOnOneLeg.rangeEquals(0, simonSays));
      *
-     *                 Buffer payMeMoney = new Buffer().writeUtf8("Pay me $1,000,000.");
-     *                 assertFalse(payMeMoney.rangeEquals(0, simonSays));
-     *               }
- * - * @param offset long - * @param bytes ByteString - * @return the long - * @throws IOException {@link java.io.IOException} IOException. + * Buffer payMeMoney = new Buffer().writeUtf8("Pay me $1,000,000."); + * assertFalse(payMeMoney.rangeEquals(0, simonSays)); + * } */ boolean rangeEquals(long offset, ByteString bytes) throws IOException; /** - * if {@code byteCount} bytes at {@code offset} in this source equal {@code bytes} - * * at {@code bytesOffset}. This expands the buffer as necessary until a byte does not match, all - * * bytes are matched, or if the stream is exhausted before enough bytes could determine a match. - * - * @param offset long - * @param bytes ByteString - * @param bytesOffset int - * @param byteCount int - * @return the true - * @throws IOException {@link java.io.IOException} IOException. + * Returns true if {@code byteCount} bytes at {@code offset} in this source equal {@code bytes} + * at {@code bytesOffset}. This expands the buffer as necessary until a byte does not match, all + * bytes are matched, or if the stream is exhausted before enough bytes could determine a match. */ boolean rangeEquals(long offset, ByteString bytes, int bytesOffset, int byteCount) throws IOException; /** - * a new {@code BufferSource} that can read data from this {@code BufferSource} + * Returns a new {@code BufferedSource} that can read data from this {@code BufferedSource} * without consuming it. The returned source becomes invalid once this source is next read or * closed. *

@@ -642,21 +584,17 @@ boolean rangeEquals(long offset, ByteString bytes, int bytesOffset, int byteCoun * * buffer.readUtf8(3) // returns "abc", buffer contains "defghi" * - * BufferSource peek = buffer.peek(); + * BufferedSource peek = buffer.peek(); * peek.readUtf8(3); // returns "def", buffer contains "defghi" * peek.readUtf8(3); // returns "ghi", buffer contains "defghi" * * buffer.readUtf8(3); // returns "def", buffer contains "ghi" * } - * - * @return the long */ BufferSource peek(); /** - * an input stream that reads from this source. - * - * @return the InputStream + * Returns an input stream that reads from this source. */ InputStream inputStream(); diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/source/GzipSource.java b/bus-core/src/main/java/org/aoju/bus/core/io/source/GzipSource.java old mode 100755 new mode 100644 index 1e441af801..a554c5c1ee --- a/bus-core/src/main/java/org/aoju/bus/core/io/source/GzipSource.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/source/GzipSource.java @@ -53,23 +53,35 @@ public class GzipSource implements Source { private static final byte SECTION_TRAILER = 2; private static final byte SECTION_DONE = 3; + /** + * Our source should yield a GZIP header (which we consume directly), followed + * by deflated bytes (which we consume via an InflaterSource), followed by a + * GZIP trailer (which we also consume directly). + */ private final BufferSource source; - + /** + * The inflater used to decompress the deflated body. + */ private final Inflater inflater; - - private final ExtractSource extractSource; - + /** + * The inflater source takes care of moving data between compressed source and + * decompressed sink buffers. + */ + private final InflaterSource inflaterSource; + /** + * Checksum used to check both the GZIP header and decompressed body. + */ private final CRC32 crc = new CRC32(); - + /** + * The current section. Always progresses forward. + */ private int section = SECTION_HEADER; public GzipSource(Source source) { - if (null == source) { - throw new IllegalArgumentException("source == null"); - } + if (source == null) throw new IllegalArgumentException("source == null"); this.inflater = new Inflater(true); this.source = IoKit.buffer(source); - this.extractSource = new ExtractSource(this.source, inflater); + this.inflaterSource = new InflaterSource(this.source, inflater); } @Override @@ -77,6 +89,7 @@ public long read(Buffer sink, long byteCount) throws IOException { if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount); if (byteCount == 0) return 0; + // If we haven't consumed the header, we must consume it before anything else. if (section == SECTION_HEADER) { consumeHeader(); section = SECTION_BODY; @@ -84,7 +97,7 @@ public long read(Buffer sink, long byteCount) throws IOException { if (section == SECTION_BODY) { long offset = sink.size; - long result = extractSource.read(sink, byteCount); + long result = inflaterSource.read(sink, byteCount); if (result != -1) { updateCrc(sink, offset, result); return result; @@ -92,10 +105,17 @@ public long read(Buffer sink, long byteCount) throws IOException { section = SECTION_TRAILER; } + // The body is exhausted; time to read the trailer. We always consume the + // trailer before returning a -1 exhausted result; that way if you read to + // the end of a GzipSource you guarantee that the CRC has been checked. if (section == SECTION_TRAILER) { consumeTrailer(); section = SECTION_DONE; + // Gzip streams self-terminate: they return -1 before their underlying + // source returns -1. Here we attempt to force the underlying stream to + // return -1 which may trigger it to release its resources. If it doesn't + // return -1, then our Gzip data finished prematurely! if (!source.exhausted()) { throw new IOException("gzip finished without exhausting source"); } @@ -105,37 +125,60 @@ public long read(Buffer sink, long byteCount) throws IOException { } private void consumeHeader() throws IOException { + // Read the 10-byte header. We peek at the flags byte first so we know if we + // need to CRC the entire header. Then we read the magic ID1ID2 sequence. + // We can skip everything else in the first 10 bytes. + // +---+---+---+---+---+---+---+---+---+---+ + // |ID1|ID2|CM |FLG| MTIME |XFL|OS | (more-->) + // +---+---+---+---+---+---+---+---+---+---+ source.require(10); - byte flags = source.buffer().getByte(3); + byte flags = source.getBuffer().getByte(3); boolean fhcrc = ((flags >> FHCRC) & 1) == 1; - if (fhcrc) updateCrc(source.buffer(), 0, 10); + if (fhcrc) updateCrc(source.getBuffer(), 0, 10); short id1id2 = source.readShort(); checkEqual("ID1ID2", (short) 0x1f8b, id1id2); source.skip(8); + // Skip optional extra fields. + // +---+---+=================================+ + // | XLEN |...XLEN bytes of "extra field"...| (more-->) + // +---+---+=================================+ if (((flags >> FEXTRA) & 1) == 1) { source.require(2); - if (fhcrc) updateCrc(source.buffer(), 0, 2); - int xlen = source.buffer().readShortLe(); + if (fhcrc) updateCrc(source.getBuffer(), 0, 2); + int xlen = source.getBuffer().readShortLe(); source.require(xlen); - if (fhcrc) updateCrc(source.buffer(), 0, xlen); + if (fhcrc) updateCrc(source.getBuffer(), 0, xlen); source.skip(xlen); } + // Skip an optional 0-terminated name. + // +=========================================+ + // |...original file name, zero-terminated...| (more-->) + // +=========================================+ if (((flags >> FNAME) & 1) == 1) { long index = source.indexOf((byte) 0); if (index == -1) throw new EOFException(); - if (fhcrc) updateCrc(source.buffer(), 0, index + 1); + if (fhcrc) updateCrc(source.getBuffer(), 0, index + 1); source.skip(index + 1); } + + // Skip an optional 0-terminated comment. + // +===================================+ + // |...file comment, zero-terminated...| (more-->) + // +===================================+ if (((flags >> FCOMMENT) & 1) == 1) { long index = source.indexOf((byte) 0); if (index == -1) throw new EOFException(); - if (fhcrc) updateCrc(source.buffer(), 0, index + 1); + if (fhcrc) updateCrc(source.getBuffer(), 0, index + 1); source.skip(index + 1); } + // Confirm the optional header CRC. + // +---+---+ + // | CRC16 | + // +---+---+ if (fhcrc) { checkEqual("FHCRC", source.readShortLe(), (short) crc.getValue()); crc.reset(); @@ -143,6 +186,10 @@ private void consumeHeader() throws IOException { } private void consumeTrailer() throws IOException { + // Read the eight-byte trailer. Confirm the body's CRC and size. + // +---+---+---+---+---+---+---+---+ + // | CRC32 | ISIZE | + // +---+---+---+---+---+---+---+---+ checkEqual("CRC", source.readIntLe(), (int) crc.getValue()); checkEqual("ISIZE", source.readIntLe(), (int) inflater.getBytesWritten()); } @@ -154,15 +201,20 @@ public Timeout timeout() { @Override public void close() throws IOException { - extractSource.close(); + inflaterSource.close(); } + /** + * Updates the CRC with the given bytes. + */ private void updateCrc(Buffer buffer, long offset, long byteCount) { + // Skip segments that we aren't checksumming. Segment s = buffer.head; for (; offset >= (s.limit - s.pos); s = s.next) { offset -= (s.limit - s.pos); } + // Checksum one segment at a time. for (; byteCount > 0; s = s.next) { int pos = (int) (s.pos + offset); int toUpdate = (int) Math.min(s.limit - pos, byteCount); diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/source/ExtractSource.java b/bus-core/src/main/java/org/aoju/bus/core/io/source/InflaterSource.java old mode 100755 new mode 100644 similarity index 74% rename from bus-core/src/main/java/org/aoju/bus/core/io/source/ExtractSource.java rename to bus-core/src/main/java/org/aoju/bus/core/io/source/InflaterSource.java index 8e5e7bdeae..e209ac2f68 --- a/bus-core/src/main/java/org/aoju/bus/core/io/source/ExtractSource.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/source/InflaterSource.java @@ -29,7 +29,6 @@ import org.aoju.bus.core.io.Segment; import org.aoju.bus.core.io.buffer.Buffer; import org.aoju.bus.core.io.timout.Timeout; -import org.aoju.bus.core.lang.Symbol; import org.aoju.bus.core.toolkit.IoKit; import java.io.EOFException; @@ -38,30 +37,34 @@ import java.util.zip.Inflater; /** - * 解压从另一个源读取的数据 - * - * @author Kimi Liu - * @since Java 17+ + * A source that uses DEFLATE + * to decompress data read from another source. */ -public class ExtractSource implements Source { +public final class InflaterSource implements Source { private final BufferSource source; private final Inflater inflater; + /** + * When we call Inflater.setInput(), the inflater keeps our byte array until + * it needs input again. This tracks how many bytes the inflater is currently + * holding on to. + */ private int bufferBytesHeldByInflater; private boolean closed; - public ExtractSource(Source source, Inflater inflater) { + public InflaterSource(Source source, Inflater inflater) { this(IoKit.buffer(source), inflater); } - ExtractSource(BufferSource source, Inflater inflater) { - if (null == source) { - throw new IllegalArgumentException("source == null"); - } - if (null == inflater) { - throw new IllegalArgumentException("inflater == null"); - } + /** + * This package-private constructor shares a buffer with its trusted caller. + * In general we can't share a BufferedSource because the inflater holds input + * bytes until they are inflated. + */ + InflaterSource(BufferSource source, Inflater inflater) { + if (source == null) throw new IllegalArgumentException("source == null"); + if (inflater == null) throw new IllegalArgumentException("inflater == null"); this.source = source; this.inflater = inflater; } @@ -76,6 +79,7 @@ public long read( while (true) { boolean sourceExhausted = refill(); + // Decompress the inflater's compressed data into the sink. try { Segment tail = sink.writableSegment(1); int toRead = (int) Math.min(byteCount, Segment.SIZE - tail.limit); @@ -88,6 +92,7 @@ public long read( if (inflater.finished() || inflater.needsDictionary()) { releaseInflatedBytes(); if (tail.pos == tail.limit) { + // We allocated a tail segment, but didn't end up needing it. Recycle! sink.head = tail.pop(); LifeCycle.recycle(tail); } @@ -100,22 +105,30 @@ public long read( } } - public final boolean refill() throws IOException { + /** + * Refills the inflater with compressed data if it needs input. (And only if + * it needs input). Returns true if the inflater required input but the source + * was exhausted. + */ + public boolean refill() throws IOException { if (!inflater.needsInput()) return false; releaseInflatedBytes(); - if (inflater.getRemaining() != 0) throw new IllegalStateException(Symbol.QUESTION_MARK); + if (inflater.getRemaining() != 0) throw new IllegalStateException("?"); - if (source.exhausted()) { - return true; - } + // If there are compressed bytes in the source, assign them to the inflater. + if (source.exhausted()) return true; - Segment head = source.buffer().head; + // Assign buffer bytes to the inflater. + Segment head = source.getBuffer().head; bufferBytesHeldByInflater = head.limit - head.pos; inflater.setInput(head.data, head.pos, bufferBytesHeldByInflater); return false; } + /** + * When the inflater has processed compressed data, remove it from the buffer. + */ private void releaseInflatedBytes() throws IOException { if (bufferBytesHeldByInflater == 0) return; int toRelease = bufferBytesHeldByInflater - inflater.getRemaining(); diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/source/PeekSource.java b/bus-core/src/main/java/org/aoju/bus/core/io/source/PeekSource.java old mode 100755 new mode 100644 index 2ccf2915fe..fa33b58e49 --- a/bus-core/src/main/java/org/aoju/bus/core/io/source/PeekSource.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/source/PeekSource.java @@ -54,29 +54,34 @@ public class PeekSource implements Source { public PeekSource(BufferSource upstream) { this.upstream = upstream; - this.buffer = upstream.buffer(); + this.buffer = upstream.getBuffer(); this.expectedSegment = buffer.head; - this.expectedPos = null != expectedSegment ? expectedSegment.pos : -1; + this.expectedPos = expectedSegment != null ? expectedSegment.pos : -1; } @Override public long read(Buffer sink, long byteCount) throws IOException { + if (byteCount < 0) throw new IllegalArgumentException("byteCount < 0: " + byteCount); if (closed) throw new IllegalStateException("closed"); - if (null != expectedSegment + // Source becomes invalid if there is an expected Segment and it and the expected position + // do not match the current head and head position of the upstream buffer + if (expectedSegment != null && (expectedSegment != buffer.head || expectedPos != buffer.head.pos)) { throw new IllegalStateException("Peek source is invalid because upstream source was used"); } + if (byteCount == 0L) return 0L; + if (!upstream.request(pos + 1)) return -1L; - upstream.request(pos + byteCount); - if (null == expectedSegment && null != buffer.head) { + if (expectedSegment == null && buffer.head != null) { + // Only once the buffer actually holds data should an expected Segment and position be + // recorded. This allows reads from the peek source to repeatedly return -1 and for data to be + // added later. Unit tests depend on this behavior. expectedSegment = buffer.head; expectedPos = buffer.head.pos; } long toCopy = Math.min(byteCount, buffer.size - pos); - if (toCopy <= 0L) return -1L; - buffer.copyTo(sink, pos, toCopy); pos += toCopy; return toCopy; @@ -88,7 +93,7 @@ public Timeout timeout() { } @Override - public void close() { + public void close() throws IOException { closed = true; } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/source/RealSource.java b/bus-core/src/main/java/org/aoju/bus/core/io/source/RealSource.java old mode 100755 new mode 100644 index 89e7677da1..7c32146dac --- a/bus-core/src/main/java/org/aoju/bus/core/io/source/RealSource.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/source/RealSource.java @@ -58,11 +58,6 @@ public RealSource(Source source) { this.source = source; } - @Override - public Buffer buffer() { - return buffer; - } - @Override public Buffer getBuffer() { return buffer; @@ -133,18 +128,18 @@ public ByteString readByteString(long byteCount) throws IOException { } @Override - public int select(Blending options) throws IOException { + public int select(Blending blending) throws IOException { if (closed) throw new IllegalStateException("closed"); while (true) { - int index = buffer.selectPrefix(options, true); + int index = buffer.selectPrefix(blending, true); if (index == -1) return -1; if (index == -2) { // We need to grow the buffer. Do that, then try it all again. if (source.read(buffer, Segment.SIZE) == -1L) return -1; } else { // We matched a full byte string: consume it and return it. - int selectedSize = options.byteStrings[index].size(); + int selectedSize = blending.byteStrings[index].size(); buffer.skip(selectedSize); return index; } @@ -478,6 +473,7 @@ public long indexOfElement(ByteString targetBytes, long fromIndex) throws IOExce long lastBufferSize = buffer.size; if (source.read(buffer, Segment.SIZE) == -1) return -1L; + // Keep searching, picking up from where we left off. fromIndex = Math.max(fromIndex, lastBufferSize); } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/source/Source.java b/bus-core/src/main/java/org/aoju/bus/core/io/source/Source.java old mode 100755 new mode 100644 diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/BOMInputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/BOMInputStream.java old mode 100755 new mode 100644 index 752e601402..4098636805 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/BOMInputStream.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/BOMInputStream.java @@ -55,17 +55,17 @@ public class BOMInputStream extends InputStream { private static final int BOM_SIZE = 4; - PushbackInputStream in; - boolean isInited = false; - String defaultCharset; - String charset; + private final PushbackInputStream in; + private final String defaultCharset; + private boolean isInited = false; + private String charset; /** * 构造 * * @param in 流 */ - public BOMInputStream(InputStream in) { + public BOMInputStream(final InputStream in) { this(in, Charset.DEFAULT_UTF_8); } @@ -75,7 +75,7 @@ public BOMInputStream(InputStream in) { * @param in 流 * @param defaultCharset 默认编码 */ - public BOMInputStream(InputStream in, String defaultCharset) { + public BOMInputStream(final InputStream in, final String defaultCharset) { this.in = new PushbackInputStream(in, BOM_SIZE); this.defaultCharset = defaultCharset; } @@ -98,7 +98,7 @@ public String getCharset() { if (false == isInited) { try { init(); - } catch (IOException ex) { + } catch (final IOException ex) { throw new InternalException(ex); } } @@ -128,8 +128,9 @@ protected void init() throws IOException { return; } - byte[] bom = new byte[BOM_SIZE]; - int n, unread; + final byte[] bom = new byte[BOM_SIZE]; + final int n; + final int unread; n = in.read(bom, 0, bom.length); if ((bom[0] == (byte) 0x00) && (bom[1] == (byte) 0x00) && (bom[2] == (byte) 0xFE) && (bom[3] == (byte) 0xFF)) { diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/BOMReader.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/BOMReader.java index 856005003d..9eb2fe3aee 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/BOMReader.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/BOMReader.java @@ -1,8 +1,37 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ package org.aoju.bus.core.io.stream; +import org.aoju.bus.core.exception.InternalException; +import org.aoju.bus.core.io.reader.ReaderWrapper; import org.aoju.bus.core.lang.Assert; -import java.io.*; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; /** * 读取带BOM头的流内容的Reader,如果非bom的流或无法识别的编码,则默认UTF-8 @@ -24,32 +53,31 @@ * @author Kimi Liu * @since Java 17+ */ -public class BOMReader extends Reader { - - private InputStreamReader reader; +public class BOMReader extends ReaderWrapper { /** * 构造 * * @param in 流 */ - public BOMReader(InputStream in) { + public BOMReader(final InputStream in) { + super(initReader(in)); + } + + /** + * 初始化为{@link InputStreamReader},将给定流转换为{@link BOMInputStream} + * + * @param in {@link InputStream} + * @return {@link InputStreamReader} + */ + private static InputStreamReader initReader(final InputStream in) { Assert.notNull(in, "InputStream must be not null!"); final BOMInputStream bin = (in instanceof BOMInputStream) ? (BOMInputStream) in : new BOMInputStream(in); try { - this.reader = new InputStreamReader(bin, bin.getCharset()); - } catch (UnsupportedEncodingException ignore) { + return new InputStreamReader(bin, bin.getCharset()); + } catch (final UnsupportedEncodingException e) { + throw new InternalException(e); } } - @Override - public int read(char[] cbuf, int off, int len) throws IOException { - return reader.read(cbuf, off, len); - } - - @Override - public void close() throws IOException { - reader.close(); - } - -} +} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/RandomFileOutputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/EmptyInputStream.java old mode 100644 new mode 100755 similarity index 74% rename from bus-core/src/main/java/org/aoju/bus/core/io/stream/RandomFileOutputStream.java rename to bus-core/src/main/java/org/aoju/bus/core/io/stream/EmptyInputStream.java index 5afd632114..f203086ff3 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/RandomFileOutputStream.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/EmptyInputStream.java @@ -25,44 +25,65 @@ ********************************************************************************/ package org.aoju.bus.core.io.stream; -import java.io.IOException; -import java.io.OutputStream; -import java.io.RandomAccessFile; +import java.io.InputStream; /** + * 空的流 + * * @author Kimi Liu * @since Java 17+ */ -public class RandomFileOutputStream extends OutputStream { +public final class EmptyInputStream extends InputStream { - private final RandomAccessFile raf; + /** + * 单例实例 + */ + public static final EmptyInputStream INSTANCE = new EmptyInputStream(); - public RandomFileOutputStream(RandomAccessFile raf) { - this.raf = raf; + private EmptyInputStream() { + + } + + @Override + public int available() { + return 0; + } + + @Override + public void close() { + } + + @Override + public void mark(final int readLimit) { + } + + @Override + public boolean markSupported() { + return true; } @Override - public void write(int b) throws IOException { - raf.write(b); + public int read() { + return -1; } @Override - public void write(byte[] b) throws IOException { - raf.write(b); + public int read(final byte[] buf) { + return -1; } @Override - public void write(byte[] b, int off, int len) throws IOException { - raf.write(b, off, len); + public int read(final byte[] buf, final int off, final int len) { + return -1; } @Override - public void flush() { + public void reset() { } @Override - public void close() throws IOException { - raf.close(); + public long skip(final long n) { + return 0L; } -} \ No newline at end of file +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/NullOutputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/EmptyOutputStream.java old mode 100755 new mode 100644 similarity index 78% rename from bus-core/src/main/java/org/aoju/bus/core/io/stream/NullOutputStream.java rename to bus-core/src/main/java/org/aoju/bus/core/io/stream/EmptyOutputStream.java index a7752f931a..8f42a25aa4 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/NullOutputStream.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/EmptyOutputStream.java @@ -35,49 +35,48 @@ * @author Kimi Liu * @since Java 17+ */ -public class NullOutputStream extends OutputStream { +public class EmptyOutputStream extends OutputStream { - private boolean closed = false; + /** + * 单例 + */ + public static final EmptyOutputStream INSTANCE = new EmptyOutputStream(); + + private EmptyOutputStream() { + + } /** - * 什么也不做,写出到 /dev/null. + * 什么也不做,写出到{@code /dev/null} * - * @param b 写出的数据 + * @param b 写出的数据 + * @param off 开始位置 + * @param len 长度 */ @Override - public void write(int b) throws IOException { - if (this.closed) _throwClosed(); + public void write(final byte[] b, final int off, final int len) { + } /** - * 什么也不做,写出到 /dev/null. + * 什么也不做,写出到 {@code /dev/null} * * @param b 写出的数据 - * @throws IOException 不抛出 */ @Override - public void write(byte[] b) throws IOException { - if (this.closed) _throwClosed(); + public void write(final int b) { + } /** - * 什么也不做,写出到/dev/null. + * 什么也不做,写出到 {@code /dev/null} * - * @param b 写出的数据 - * @param off 开始位置 - * @param len 长度 + * @param b 写出的数据 + * @throws IOException 不抛出 */ @Override - public void write(byte[] b, int off, int len) throws IOException { - if (this.closed) _throwClosed(); - } - - private void _throwClosed() throws IOException { - throw new IOException("This OutputStream has been closed"); - } + public void write(final byte[] b) throws IOException { - public void close() { - this.closed = true; } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/FastByteOutputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/FastByteOutputStream.java old mode 100755 new mode 100644 index 1fadcb53ea..03bcfccae6 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/FastByteOutputStream.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/FastByteOutputStream.java @@ -27,17 +27,20 @@ import org.aoju.bus.core.exception.InternalException; import org.aoju.bus.core.io.buffer.FastByteBuffer; -import org.aoju.bus.core.lang.Charset; -import org.aoju.bus.core.lang.Normal; import org.aoju.bus.core.toolkit.ObjectKit; import java.io.IOException; import java.io.OutputStream; +import java.nio.charset.Charset; /** - * 基于快速缓冲FastByteBuffer的OutputStream,自动扩充缓冲区 + * 基于快速缓冲FastByteBuffer的OutputStream,随着数据的增长自动扩充缓冲区 + *

* 可以通过{@link #toByteArray()}和 {@link #toString()}来获取数据 - * 避免重新分配内存块而是分配新增的缓冲区,缓冲区不会被GC,数据也不会被拷贝到其他缓冲区 + *

+ * {@link #close()}方法无任何效果,当流被关闭后不会抛出IOException + *

+ * 这种设计避免重新分配内存块而是分配新增的缓冲区,缓冲区不会被GC,数据也不会被拷贝到其他缓冲区。 * * @author Kimi Liu * @since Java 17+ @@ -46,8 +49,11 @@ public class FastByteOutputStream extends OutputStream { private final FastByteBuffer buffer; + /** + * 构造 + */ public FastByteOutputStream() { - this(Normal._1024); + this(1024); } /** @@ -55,17 +61,17 @@ public FastByteOutputStream() { * * @param size 预估大小 */ - public FastByteOutputStream(int size) { + public FastByteOutputStream(final int size) { buffer = new FastByteBuffer(size); } @Override - public void write(byte[] b, int off, int len) { + public void write(final byte[] b, final int off, final int len) { buffer.append(b, off, len); } @Override - public void write(int b) { + public void write(final int b) { buffer.append((byte) b); } @@ -74,10 +80,10 @@ public int size() { } /** - * 此方法无任何效果,当流被关闭后不会抛出IOException + * 此方法无任何效果,当流被关闭后不会抛出IOException */ @Override - public void close() throws IOException { + public void close() { // nop } @@ -91,9 +97,10 @@ public void reset() { * @param out 输出流 * @throws InternalException IO异常 */ - public void writeTo(OutputStream out) throws InternalException { + public void writeTo(final OutputStream out) throws InternalException { final int index = buffer.index(); if (index < 0) { + // 无数据写出 return; } byte[] buf; @@ -103,11 +110,12 @@ public void writeTo(OutputStream out) throws InternalException { out.write(buf); } out.write(buffer.array(index), 0, buffer.offset()); - } catch (IOException e) { + } catch (final IOException e) { throw new InternalException(e); } } + /** * 转为Byte数组 * @@ -119,27 +127,18 @@ public byte[] toByteArray() { @Override public String toString() { - return toString(Charset.defaultCharset()); - } - - /** - * 转为字符串 - * - * @param charsetName 编码 - * @return 字符串 - */ - public String toString(String charsetName) { - return toString(Charset.charset(charsetName)); + return toString(org.aoju.bus.core.lang.Charset.defaultCharset()); } /** * 转为字符串 * - * @param charset 编码 + * @param charset 编码,null表示默认编码 * @return 字符串 */ - public String toString(java.nio.charset.Charset charset) { - return new String(toByteArray(), ObjectKit.defaultIfNull(charset, Charset::defaultCharset)); + public String toString(final Charset charset) { + return new String(toByteArray(), + ObjectKit.defaultIfNull(charset, Charset::defaultCharset)); } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/ObjectInputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/ObjectInputStream.java new file mode 100644 index 0000000000..321b18bf2b --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/ObjectInputStream.java @@ -0,0 +1,125 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.io.stream; + +import org.aoju.bus.core.toolkit.CollKit; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InvalidClassException; +import java.io.ObjectStreamClass; +import java.util.HashSet; +import java.util.Set; + +/** + * 带有类验证的对象流,用于避免反序列化漏洞 + * + * @author Kimi Liu + * @since Java 17+ + */ +public class ObjectInputStream extends java.io.ObjectInputStream { + + private Set whiteClassSet; + private Set blackClassSet; + + /** + * 构造 + * + * @param inputStream 流 + * @param acceptClasses 白名单的类 + * @throws IOException IO异常 + */ + public ObjectInputStream(final InputStream inputStream, final Class... acceptClasses) throws IOException { + super(inputStream); + accept(acceptClasses); + } + + /** + * 禁止反序列化的类,用于反序列化验证 + * + * @param refuseClasses 禁止反序列化的类 + */ + public void refuse(final Class... refuseClasses) { + if (null == this.blackClassSet) { + this.blackClassSet = new HashSet<>(); + } + for (final Class acceptClass : refuseClasses) { + this.blackClassSet.add(acceptClass.getName()); + } + } + + /** + * 接受反序列化的类,用于反序列化验证 + * + * @param acceptClasses 接受反序列化的类 + */ + public void accept(final Class... acceptClasses) { + if (null == this.whiteClassSet) { + this.whiteClassSet = new HashSet<>(); + } + for (final Class acceptClass : acceptClasses) { + this.whiteClassSet.add(acceptClass.getName()); + } + } + + /** + * 只允许反序列化SerialObject class + */ + @Override + protected Class resolveClass(final ObjectStreamClass desc) throws IOException, ClassNotFoundException { + validateClassName(desc.getName()); + return super.resolveClass(desc); + } + + /** + * 验证反序列化的类是否合法 + * + * @param className 类名 + * @throws InvalidClassException 非法类 + */ + private void validateClassName(final String className) throws InvalidClassException { + // 黑名单 + if (CollKit.isNotEmpty(this.blackClassSet)) { + if (this.blackClassSet.contains(className)) { + throw new InvalidClassException("Unauthorized deserialization attempt by black list", className); + } + } + + if (CollKit.isEmpty(this.whiteClassSet)) { + return; + } + if (className.startsWith("java.")) { + // java中的类默认在白名单中 + return; + } + if (this.whiteClassSet.contains(className)) { + return; + } + + throw new InvalidClassException("Unauthorized deserialization attempt", className); + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/QueueInputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/QueueInputStream.java deleted file mode 100644 index 32f448a6b0..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/QueueInputStream.java +++ /dev/null @@ -1,230 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.io.stream; - -import org.aoju.bus.core.lang.Symbol; - -import java.io.IOException; -import java.io.InputStream; -import java.util.LinkedList; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class QueueInputStream extends InputStream { - - //原始流 - private final InputStream is; - //缓存 - private final LinkedList cache = new LinkedList<>(); - //peek索引 - private int peekindex = 0; - //是否到流尾 - private boolean end = false; - //列 - private int col = 0; - //行 - private int row = 1; - - public QueueInputStream(InputStream is) { - this.is = is; - } - - public int read() throws IOException { - return poll(); - } - - /** - * 读取一项数据 - * - * @param ends 结束符, 默认' ', '\r', '\n' - * @return 数据 - * @throws IOException 异常 - */ - public String readItem(char... ends) throws IOException { - StringBuilder sb = new StringBuilder(); - while (true) { - switch (peek()) { - case Symbol.C_SPACE: - case Symbol.C_CR: - case Symbol.C_LF: - case -1: - return sb.toString(); - default: - for (Character c : ends) { - if (c.charValue() == peek()) { - return sb.toString(); - } - } - sb.append((char) poll()); - } - } - } - - /** - * 读取一行 - * - * @return 一行数据 - * @throws IOException 异常 - */ - public String readLine() throws IOException { - StringBuilder sb = new StringBuilder(); - for (; ; ) { - int v = peek(); - if (v == Symbol.C_CR || v == Symbol.C_LF) { - poll(); - v = peekNext(); - if (v == Symbol.C_CR || v == Symbol.C_LF) { - poll(); - } - break; - } - sb.append((char) poll()); - } - return sb.toString(); - } - - /** - * 读取头部字节, 并删除 - * - * @return 头部字节 - * @throws IOException 异常 - */ - public int poll() throws IOException { - peekindex = 0; - int v = -1; - if (cache.size() <= 0) { - v = is.read(); - } else { - v = cache.poll(); - } - if (v == -1) { - end = true; - } - if (v == Symbol.C_LF) { - col = 0; - row++; - } else { - col++; - } - return v; - } - - /** - * 访问头部开始第几个字节, 不删除 - * - * @param index 索引 - * @return 头部的第N个字节 - * @throws IOException 异常 - */ - public int peek(int index) throws IOException { - while (cache.size() <= index) { - cache.add(is.read()); - } - return cache.get(index); - } - - /** - * 访问上次peekNext访问的下个位置的字节, 未访问过则访问索引0, poll, peek后归零, 不删除 - * - * @return 下一个位置的字节 - * @throws IOException 异常 - */ - public int peekNext() throws IOException { - return peek(peekindex++); - } - - /** - * 访问头部字节, 不删除 - * - * @return 头部字节 - * @throws IOException 异常 - */ - public int peek() throws IOException { - peekindex = 0; - int v = peek(peekindex++); - if (v == -1) { - end = true; - } - return v; - } - - /** - * 跳过和丢弃输入流中的数据 - */ - public long skip(long n) throws IOException { - int s = cache.size(); - if (s > 0) { - if (s < n) { - n = n - s; - } else { - for (int i = 0; i < n; i++) { - cache.poll(); - } - return n; - } - } - return super.skip(n) + s; - } - - /** - * 是否结束 - * - * @return true 如果已经结束 - */ - public boolean isEnd() { - return end; - } - - - /** - * 是否以 start 开始 - * - * @param start 开始位置 - * @return true, 如果的确以指定字符串开始 - * @throws IOException 异常 - */ - public boolean startWith(String start) throws IOException { - char[] cs = start.toCharArray(); - int i = 0; - for (; i < cs.length; i++) { - if (peek(i) != cs[i]) { - return false; - } - } - return true; - } - - public int getCol() { - return col; - } - - public int getRow() { - return row; - } - -} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/QueueReader.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/QueueReader.java deleted file mode 100644 index b99d6a86ea..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/QueueReader.java +++ /dev/null @@ -1,242 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.io.stream; - -import org.aoju.bus.core.lang.Symbol; - -import java.io.IOException; -import java.io.Reader; -import java.util.LinkedList; - -/** - * 队列InputStream - * - * @author Kimi Liu - * @since Java 17+ - */ -public class QueueReader extends Reader { - - //原始流 - private final Reader is; - //缓存 - private final LinkedList cache = new LinkedList<>(); - //peek索引 - private int peekindex = 0; - //是否到流尾 - private boolean end = false; - //列 - private int col = 0; - //行 - private int row = 1; - - public QueueReader(Reader is) { - this.is = is; - } - - /** - * 读取一项数据 - * - * @param ends 结束符, 默认' ', '\r', '\n' - * @return 数据 - * @throws IOException 异常 - */ - public String readItem(char... ends) throws IOException { - StringBuilder sb = new StringBuilder(); - while (true) { - switch (peek()) { - case Symbol.C_SPACE: - case Symbol.C_CR: - case Symbol.C_LF: - case -1: - return sb.toString(); - default: - for (Character c : ends) { - if (c.charValue() == peek()) { - return sb.toString(); - } - } - sb.append((char) poll()); - } - } - } - - /** - * 读取一行 - * - * @return 一行数据 - * @throws IOException 异常 - */ - public String readLine() throws IOException { - StringBuilder sb = new StringBuilder(); - for (; ; ) { - int v = peek(); - if (v == Symbol.C_CR || v == Symbol.C_LF) { - poll(); - v = peekNext(); - if (v == Symbol.C_CR || v == Symbol.C_LF) { - poll(); - } - break; - } - sb.append((char) poll()); - } - return sb.toString(); - } - - /** - * 读取头部字节, 并删除 - * - * @return 头部字符 - * @throws IOException 异常 - */ - public int poll() throws IOException { - peekindex = 0; - int v = -1; - if (cache.size() <= 0) { - v = is.read(); - } else { - v = cache.poll(); - } - if (v == -1) { - end = true; - } - if (v == Symbol.C_LF) { - col = 0; - row++; - } else { - col++; - } - return v; - } - - /** - * 访问头部开始第几个字节, 不删除 - * - * @param index 索引 - * @return 头部的第N个字符 - * @throws IOException 异常 - */ - public int peek(int index) throws IOException { - while (cache.size() <= index) { - cache.add(is.read()); - } - return cache.get(index); - } - - /** - * 访问上次peekNext访问的下个位置的字节, 未访问过则访问索引0, poll, peek后归零, 不删除 - * - * @return 下一个位置的字符 - * @throws IOException 异常 - */ - public int peekNext() throws IOException { - return peek(peekindex++); - } - - /** - * 访问头部字节, 不删除 - * - * @return 头部字符 - * @throws IOException 异常 - */ - public int peek() throws IOException { - peekindex = 0; - int v = peek(peekindex++); - if (v == -1) { - end = true; - } - return v; - } - - /** - * 跳过和丢弃输入流中的数据 - */ - public long skip(long n) throws IOException { - int s = cache.size(); - if (s > 0) { - if (s < n) { - n = n - s; - } else { - for (int i = 0; i < n; i++) { - cache.poll(); - } - return n; - } - } - return super.skip(n) + s; - } - - /** - * 是否结束 - * - * @return true, 如果流已经结束 - */ - public boolean isEnd() { - return end; - } - - public int read(char[] cbuf, int off, int len) throws IOException { - for (int i = 0; i < len; i++) { - if (isEnd()) { - return -1; - } - cbuf[off + i] = (char) poll(); - } - return len; - } - - public void close() throws IOException { - is.close(); - cache.clear(); - } - - /** - * 是否以 start 开始 - * - * @param start 开始位置 - * @return true, 如果的确以指定字符串开始 - * @throws IOException 异常 - */ - public boolean startWith(String start) throws IOException { - char[] cs = start.toCharArray(); - int i = 0; - for (; i < cs.length; i++) { - if (peek(i) != cs[i]) { - return false; - } - } - return true; - } - - public int getCol() { - return col; - } - - public int getRow() { - return row; - } - -} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StreamBuffer.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/StreamBuffer.java deleted file mode 100644 index f771710489..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StreamBuffer.java +++ /dev/null @@ -1,132 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.io.stream; - -import org.aoju.bus.core.exception.InternalException; -import org.aoju.bus.core.lang.Charset; -import org.aoju.bus.core.lang.Normal; -import org.aoju.bus.core.toolkit.IoKit; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class StreamBuffer extends InputStream { - - private final OutputStreamBuffer buffer = new OutputStreamBuffer(); - private int index = 0; - private int cursor = 0; - - public OutputStream getBuffer() { - return buffer; - } - - public void write(int b) throws IOException { - buffer.write(b); - } - - @Override - public int read() throws IOException { - if (cursor > buffer.width) { - index++; - cursor = 0; - } - if (index > buffer.index) - return -1; - if (index < buffer.bytes.size()) { - byte[] cs = buffer.bytes.get(index); - if (cursor < buffer.cursor) - return cs[cursor++]; - } - return -1; - } - - @Override - public int available() { - return buffer.size(); - } - - @Override - public synchronized void reset() { - index = 0; - cursor = 0; - } - - @Override - public String toString() { - try { - return toString(Charset.DEFAULT_CHARSET); - } catch (IOException e) { - throw new InternalException(e); - } - } - - public String toString(String charset) throws IOException { - index = 0; - cursor = 0; - StringBuilder sb = new StringBuilder(); - StringOutputStream sos = new StringOutputStream(sb, charset); - byte c; - while ((c = (byte) this.read()) != -1) - sos.write(c); - sos.flush(); - IoKit.close(sos); - return sb.toString(); - } - - private static class OutputStreamBuffer extends OutputStream { - - private final List bytes = new ArrayList<>(); - private final int width = Normal._1024; - private int index = 0; - private int cursor = 0; - - @Override - public void write(int b) throws IOException { - if (cursor >= width) - index++; - byte[] row = bytes.size() > index ? bytes.get(index) : null; - if (null == row) { - row = new byte[width]; - bytes.add(row); - cursor = 0; - } - row[cursor++] = (byte) b; - } - - private int size() { - return index > 0 ? width * (index - 1) + cursor : cursor; - } - - } - -} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StreamReader.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/StreamReader.java new file mode 100755 index 0000000000..01f3a5ab86 --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/StreamReader.java @@ -0,0 +1,204 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************//********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.io.stream; + +import org.aoju.bus.core.exception.InternalException; +import org.aoju.bus.core.toolkit.IoKit; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * {@link InputStream}读取器 + * + * @author Kimi Liu + * @since Java 17+ + */ +public class StreamReader { + + private final InputStream in; + private final boolean closeAfterRead; + + /** + * 构造 + * + * @param in {@link InputStream} + * @param closeAfterRead 读取结束后是否关闭输入流 + */ + public StreamReader(final InputStream in, final boolean closeAfterRead) { + this.in = in; + this.closeAfterRead = closeAfterRead; + } + + /** + * 创建读取器 + * + * @param in {@link InputStream} + * @param closeAfterRead 读取结束后是否关闭输入流 + * @return StreamReader + */ + public static StreamReader of(final InputStream in, final boolean closeAfterRead) { + return new StreamReader(in, closeAfterRead); + } + + /** + * 从流中读取bytes + * + * @return bytes + * @throws InternalException IO异常 + */ + public byte[] readBytes() throws InternalException { + return readBytes(-1); + } + + /** + * 读取指定长度的byte数组 + * + * @param length 长度,小于0表示读取全部 + * @return bytes + * @throws InternalException IO异常 + */ + public byte[] readBytes(final int length) throws InternalException { + final InputStream in = this.in; + if (null == in || length == 0) { + return new byte[0]; + } + return read(length).toByteArray(); + } + + /** + * 从流中读取内容,读到输出流中,读取完毕后可选是否关闭流 + * + * @return 输出流 + * @throws InternalException IO异常 + */ + public FastByteOutputStream read() throws InternalException { + return read(-1); + } + + /** + * 从流中读取内容,读到输出流中,读取完毕后可选是否关闭流 + * + * @param limit 限制最大拷贝长度,-1表示无限制 + * @return 输出流 + * @throws InternalException IO异常 + */ + public FastByteOutputStream read(final int limit) throws InternalException { + final InputStream in = this.in; + final FastByteOutputStream out; + if (in instanceof FileInputStream) { + // 文件流的长度是可预见的,此时直接读取效率更高 + try { + int length = in.available(); + if (limit > 0 && limit < length) { + length = limit; + } + out = new FastByteOutputStream(length); + } catch (final IOException e) { + throw new InternalException(e); + } + } else { + out = new FastByteOutputStream(); + } + try { + IoKit.copy(in, out, IoKit.DEFAULT_BUFFER_SIZE, limit, null); + } finally { + if (closeAfterRead) { + IoKit.close(in); + } + } + return out; + } + + /** + * 从流中读取对象,即对象的反序列化 + * + *

注意!!! 此方法不会检查反序列化安全,可能存在反序列化漏洞风险!!!

+ * + *

+ * 此方法使用了{@link ObjectInputStream}中的黑白名单方式过滤类,用于避免反序列化漏洞
+ * 通过构造{@link ObjectInputStream},调用{@link ObjectInputStream#accept(Class[])} + * 或者{@link ObjectInputStream#refuse(Class[])}方法添加可以被序列化的类或者禁止序列化的类。 + *

+ * + * @param 读取对象的类型 + * @param acceptClasses 读取对象类型 + * @return 输出流 + * @throws InternalException IO异常 + */ + public T readObject(final Class... acceptClasses) throws InternalException { + final InputStream in = this.in; + if (null == in) { + return null; + } + + // 转换 + final ObjectInputStream validateIn; + if (in instanceof ObjectInputStream) { + validateIn = (ObjectInputStream) in; + validateIn.accept(acceptClasses); + } else { + try { + validateIn = new ObjectInputStream(in, acceptClasses); + } catch (final IOException e) { + throw new InternalException(e); + } + } + + // 读取 + try { + return (T) validateIn.readObject(); + } catch (final ClassNotFoundException | IOException e) { + throw new InternalException(e); + } + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StreamWriter.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/StreamWriter.java new file mode 100755 index 0000000000..06cf7b946e --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/StreamWriter.java @@ -0,0 +1,142 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.io.stream; + +import org.aoju.bus.core.convert.Convert; +import org.aoju.bus.core.exception.InternalException; +import org.aoju.bus.core.lang.Normal; +import org.aoju.bus.core.toolkit.IoKit; + +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.nio.charset.Charset; + +/** + * {@link OutputStream}写出器 + * + * @author Kimi Liu + * @since Java 17+ + */ +public class StreamWriter { + + private final OutputStream out; + private final boolean closeAfterWrite; + + /** + * 构造 + * + * @param out {@link OutputStream} + * @param closeAfterWrite 写出结束后是否关闭流 + */ + public StreamWriter(final OutputStream out, final boolean closeAfterWrite) { + this.out = out; + this.closeAfterWrite = closeAfterWrite; + } + + /** + * 创建写出器 + * + * @param out {@link OutputStream} + * @param closeAfterWrite 写出结束后是否关闭流 + * @return StreamReader + */ + public static StreamWriter of(final OutputStream out, final boolean closeAfterWrite) { + return new StreamWriter(out, closeAfterWrite); + } + + /** + * 将byte[]写到流中 + * + * @param content 写入的内容 + * @throws InternalException IO异常 + */ + public void write(final byte[] content) throws InternalException { + final OutputStream out = this.out; + try { + out.write(content); + } catch (final IOException e) { + throw new InternalException(e); + } finally { + if (closeAfterWrite) { + IoKit.close(out); + } + } + } + + /** + * 将多部分对象写到流中,使用{@link ObjectOutputStream},对象必须实现序列化接口 + * + * @param contents 写入的内容 + * @throws InternalException IO异常 + */ + public void writeObject(final Object... contents) throws InternalException { + ObjectOutputStream osw = null; + try { + osw = out instanceof ObjectOutputStream ? (ObjectOutputStream) out : new ObjectOutputStream(out); + for (final Object content : contents) { + if (content != null) { + osw.writeObject(content); + } + } + osw.flush(); + } catch (final IOException e) { + throw new InternalException(e); + } finally { + if (closeAfterWrite) { + IoKit.close(osw); + } + } + } + + /** + * 将多部分内容写到流中,自动转换为字符串 + * + * @param charset 写出的内容的字符集 + * @param contents 写入的内容,调用toString()方法,不包括不会自动换行 + * @throws InternalException IO异常 + */ + public void writeString(final Charset charset, final Object... contents) throws InternalException { + OutputStreamWriter osw = null; + try { + osw = IoKit.getWriter(out, charset); + for (final Object content : contents) { + if (content != null) { + osw.write(Convert.toString(content, Normal.EMPTY)); + } + } + osw.flush(); + } catch (final IOException e) { + throw new InternalException(e); + } finally { + if (closeAfterWrite) { + IoKit.close(osw); + } + } + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringInputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringInputStream.java old mode 100644 new mode 100755 index f88bd5f433..3918f21029 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringInputStream.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringInputStream.java @@ -25,37 +25,38 @@ ********************************************************************************/ package org.aoju.bus.core.io.stream; -import org.aoju.bus.core.exception.InternalException; -import org.aoju.bus.core.lang.Charset; +import org.aoju.bus.core.toolkit.StringKit; import java.io.ByteArrayInputStream; -import java.io.UnsupportedEncodingException; +import java.nio.charset.Charset; /** + * 基于字符串的InputStream + * * @author Kimi Liu * @since Java 17+ */ public class StringInputStream extends ByteArrayInputStream { - public StringInputStream(CharSequence s, java.nio.charset.Charset charset) { - super(toBytes(s, charset)); + /** + * 构造 + * + * @param text 字符串 + * @param charset 编码 + */ + public StringInputStream(final CharSequence text, final Charset charset) { + super(StringKit.bytes(text, charset)); } - public StringInputStream(CharSequence s) { - super(toBytes(s, Charset.UTF_8)); + /** + * 创建StrInputStream + * + * @param text 字符串 + * @param charset 编码 + * @return StrInputStream + */ + public static StringInputStream of(final CharSequence text, final Charset charset) { + return new StringInputStream(text, charset); } - protected static byte[] toBytes(CharSequence text, java.nio.charset.Charset charset) { - if (null == text) - return new byte[0]; - if (null == charset) { - charset = Charset.UTF_8; - } - try { - return text.toString().getBytes(charset.name()); - } catch (UnsupportedEncodingException e) { - throw new InternalException(e); - } - } - -} \ No newline at end of file +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringOutputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringOutputStream.java deleted file mode 100644 index ce891f8af4..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringOutputStream.java +++ /dev/null @@ -1,93 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.io.stream; - -import org.aoju.bus.core.lang.Charset; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class StringOutputStream extends OutputStream { - - private final StringBuilder sb; - private final String charset; - private ByteArrayOutputStream baos; - - public StringOutputStream(StringBuilder sb) { - this(sb, Charset.DEFAULT_UTF_8); - } - - public StringOutputStream(StringBuilder sb, String charset) { - this.sb = sb; - baos = new ByteArrayOutputStream(); - this.charset = charset; - } - - /** - * 完成本方法后,确认字符串已经完成写入后,务必调用flash方法! - */ - @Override - public void write(int b) throws IOException { - if (null == baos) - throw new IOException("Stream is closed"); - baos.write(b); - } - - /** - * 使用StringBuilder前,务必调用 - */ - @Override - public void flush() throws IOException { - if (null != baos) { - baos.flush(); - if (baos.size() > 0) { - if (null == charset) { - sb.append(baos.toString()); - } else { - sb.append(baos.toString(charset)); - } - - baos.reset(); - } - } - } - - @Override - public void close() throws IOException { - flush(); - baos = null; - } - - public StringBuilder getStringBuilder() { - return sb; - } - -} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringReader.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringReader.java deleted file mode 100644 index 21611353c5..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringReader.java +++ /dev/null @@ -1,63 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.io.stream; - -import java.io.IOException; -import java.io.Reader; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class StringReader extends Reader { - - private final CharSequence cs; - private int index; - - public StringReader(CharSequence cs) { - this.cs = cs; - index = 0; - } - - @Override - public void close() throws IOException { - } - - @Override - public int read(char[] cbuf, int off, int len) throws IOException { - if (index >= cs.length()) - return -1; - int count = 0; - for (int i = off; i < (off + len); i++) { - if (index >= cs.length()) - return count; - cbuf[i] = cs.charAt(index++); - count++; - } - return count; - } - -} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringWriter.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringWriter.java deleted file mode 100644 index 31afaf795f..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/StringWriter.java +++ /dev/null @@ -1,62 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.io.stream; - -import java.io.IOException; -import java.io.Writer; - -/** - * @author Kimi Liu - * @since Java 17+ - */ -public class StringWriter extends Writer { - - private final StringBuilder sb; - - public StringWriter(StringBuilder sb) { - this.sb = sb; - } - - @Override - public void close() throws IOException { - } - - @Override - public void flush() throws IOException { - } - - @Override - public void write(char[] cbuf, int off, int len) throws IOException { - for (int i = off; i < (off + len); i++) { - sb.append(cbuf[i]); - } - } - - public StringBuilder getStringBuilder() { - return sb; - } - -} \ No newline at end of file diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/SyncInputStream.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/SyncInputStream.java new file mode 100755 index 0000000000..4b057a54c0 --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/SyncInputStream.java @@ -0,0 +1,134 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.io.stream; + +import org.aoju.bus.core.exception.InternalException; +import org.aoju.bus.core.io.Progress; +import org.aoju.bus.core.toolkit.IoKit; +import org.aoju.bus.core.toolkit.StringKit; + +import java.io.*; + +/** + * 同步流,可将包装的流同步为ByteArrayInputStream,以便持有内容并关闭原流 + * + * @author Kimi Liu + * @since Java 17+ + */ +public class SyncInputStream extends FilterInputStream { + + private final long length; + private final boolean isIgnoreEOFError; + /** + * 是否异步,异步下只持有流,否则将在初始化时直接读取body内容 + */ + private volatile boolean asyncFlag = true; + + /** + * 构造
+ * 如果isAsync为{@code true},则直接持有原有流,{@code false},则将流中内容,按照给定length读到{@link ByteArrayInputStream}中备用 + * + * @param in 数据流 + * @param length 限定长度,-1表示未知长度 + * @param isAsync 是否异步 + * @param isIgnoreEOFError 是否忽略EOF错误,在Http协议中,对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束
+ * 如果服务端未遵循这个规范或响应没有正常结束,会报EOF异常,此选项用于是否忽略这个异常。
+ */ + public SyncInputStream(final InputStream in, final long length, final boolean isAsync, final boolean isIgnoreEOFError) { + super(in); + this.length = length; + this.isIgnoreEOFError = isIgnoreEOFError; + if (false == isAsync) { + sync(); + } + } + + /** + * 是否为EOF异常,包括
+ * + * + * @param e 异常 + * @return 是否EOF异常 + */ + private static boolean isEOFException(final Throwable e) { + if (e instanceof FileNotFoundException) { + // 服务器无返回内容,忽略之 + return true; + } + return e instanceof EOFException || StringKit.containsIgnoreCase(e.getMessage(), "Premature EOF"); + } + + /** + * 同步数据到内存 + */ + public void sync() { + if (false == asyncFlag) { + // 已经是同步模式 + return; + } + + this.in = new ByteArrayInputStream(readBytes()); + this.asyncFlag = false; + } + + /** + * 读取流中所有bytes + * + * @return bytes + */ + public byte[] readBytes() { + final FastByteOutputStream bytesOut = new FastByteOutputStream(length > 0 ? (int) length : 1024); + final long length = copyTo(bytesOut, null); + return length > 0 ? bytesOut.toByteArray() : new byte[0]; + } + + /** + * 将流的内容拷贝到输出流 + * + * @param out 输出流 + * @param streamProgress 进度条 + * @return 拷贝长度 + */ + public long copyTo(final OutputStream out, final Progress streamProgress) { + long copyLength = -1; + try { + copyLength = IoKit.copy(this.in, out, IoKit.DEFAULT_BUFFER_SIZE, this.length, streamProgress); + } catch (final InternalException e) { + if (false == (isIgnoreEOFError && isEOFException(e.getCause()))) { + throw e; + } + // 忽略读取流中的EOF错误 + } finally { + // 读取结束 + IoKit.close(in); + } + return copyLength; + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/stream/package-info.java b/bus-core/src/main/java/org/aoju/bus/core/io/stream/package-info.java old mode 100644 new mode 100755 index ce79a9aa30..cc741fad96 --- a/bus-core/src/main/java/org/aoju/bus/core/io/stream/package-info.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/stream/package-info.java @@ -1,5 +1,5 @@ /** - * Java8的stream相关封装 + * InputStream和OutputStream相关方法和类封装 * * @author Kimi Liu * @since Java 17+ diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/timout/AssignTimeout.java b/bus-core/src/main/java/org/aoju/bus/core/io/timout/AssignTimeout.java old mode 100755 new mode 100644 index 0d00d05229..c5f3e89355 --- a/bus-core/src/main/java/org/aoju/bus/core/io/timout/AssignTimeout.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/timout/AssignTimeout.java @@ -45,6 +45,9 @@ public AssignTimeout(Timeout delegate) { this.delegate = delegate; } + /** + * {@link Timeout} instance to which this instance is currently delegating. + */ public final Timeout delegate() { return delegate; } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/timout/AsyncTimeout.java b/bus-core/src/main/java/org/aoju/bus/core/io/timout/AsyncTimeout.java old mode 100755 new mode 100644 index e6a3b01ff5..01fb6a2823 --- a/bus-core/src/main/java/org/aoju/bus/core/io/timout/AsyncTimeout.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/timout/AsyncTimeout.java @@ -29,7 +29,6 @@ import org.aoju.bus.core.io.buffer.Buffer; import org.aoju.bus.core.io.sink.Sink; import org.aoju.bus.core.io.source.Source; -import org.aoju.bus.core.lang.Normal; import org.aoju.bus.core.toolkit.IoKit; import java.io.IOException; @@ -44,47 +43,57 @@ * @since Java 17+ */ public class AsyncTimeout extends Timeout { + /** - * 一次不要写超过64 KiB的数据,否则,慢速连接可能会遭受超时 + * Don't write more than 64 KiB of data at a time, give or take a segment. Otherwise slow + * connections may suffer timeouts even when they're making (slow) progress. Without this, writing + * a single 1 MiB buffer may never succeed on a sufficiently slow connection. */ - private static final int TIMEOUT_WRITE_SIZE = Normal._64 * Normal._1024; + private static final int TIMEOUT_WRITE_SIZE = 64 * 1024; + /** - * 任务线程在关闭之前的空闲时间 + * Duration for the watchdog thread to be idle before it shuts itself down. */ private static final long IDLE_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60); private static final long IDLE_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(IDLE_TIMEOUT_MILLIS); + /** - * 线程处理一个挂起超时的链表,按要触发的顺序排序。该类在AsyncTimeout.class上同步。这个锁保护队列 + * The watchdog thread processes a linked list of pending timeouts, sorted in the order to be + * triggered. This class synchronizes on AsyncTimeout.class. This lock guards the queue. + * + *

Head's 'next' points to the first element of the linked list. The first element is the next + * node to time out, or null if the queue is empty. The head is null until the watchdog thread is + * started and also after being idle for {@link #IDLE_TIMEOUT_MILLIS}. */ static AsyncTimeout head; + /** - * 如果此节点当前在队列中,则为True + * True if this node is currently in the queue. */ private boolean inQueue; + /** - * 链表中的下一个节点 + * The next node in the linked list. */ private AsyncTimeout next; /** - * 这就是任务超时时间 + * If scheduled, this is the time that the watchdog should time this out. */ private long timeoutAt; private static synchronized void scheduleTimeout( - AsyncTimeout node, - long timeoutNanos, - boolean hasDeadline) { - // 启动任务线程,并在安排第一次超时时创建head节点 - if (null == head) { + AsyncTimeout node, long timeoutNanos, boolean hasDeadline) { + // Start the watchdog thread and create the head node when the first timeout is scheduled. + if (head == null) { head = new AsyncTimeout(); new Watchdog().start(); } long now = System.nanoTime(); if (timeoutNanos != 0 && hasDeadline) { - // 计算最早的事件;要么超时,要么截止。因为nanoTime可以封装, - // 所以Math.min()对于绝对值没有定义,但是对于相对值有意义 + // Compute the earliest event; either timeout or deadline. Because nanoTime can wrap around, + // Math.min() is undefined for absolute values, but meaningful for relative ones. node.timeoutAt = now + Math.min(timeoutNanos, node.deadlineNanoTime() - now); } else if (timeoutNanos != 0) { node.timeoutAt = now + timeoutNanos; @@ -93,15 +102,15 @@ private static synchronized void scheduleTimeout( } else { throw new AssertionError(); } - // 按排序顺序插入节点 + + // Insert the node in sorted order. long remainingNanos = node.remainingNanos(now); for (AsyncTimeout prev = head; true; prev = prev.next) { - if (null == prev.next || remainingNanos < prev.next.remainingNanos(now)) { + if (prev.next == null || remainingNanos < prev.next.remainingNanos(now)) { node.next = prev.next; prev.next = node; if (prev == head) { - // 在前面插入时,唤醒任务 - AsyncTimeout.class.notify(); + AsyncTimeout.class.notify(); // Wake up the watchdog when inserting at the front. } break; } @@ -109,81 +118,73 @@ private static synchronized void scheduleTimeout( } /** - * 如果超时发生,则返回true - * - * @param node 节点信息 - * @return the true/false + * Returns true if the timeout occurred. */ private static synchronized boolean cancelScheduledTimeout(AsyncTimeout node) { - // 从链表中删除节点 - for (AsyncTimeout prev = head; null != prev; prev = prev.next) { + // Remove the node from the linked list. + for (AsyncTimeout prev = head; prev != null; prev = prev.next) { if (prev.next == node) { prev.next = node.next; node.next = null; return false; } } - // 在链表中没有找到节点:它一定超时了! + + // The node wasn't found in the linked list: it must have timed out! return true; } /** - * 删除并返回列表顶部的节点,如有必要,等待它超时。 - * 如果在开始时列表的头部没有节点,并且在等待{@code IDLE_TIMEOUT_NANOS}之后仍然没有节点, - * 则返回{@link #head}。如果在等待时插入了新节点,则返回null。否则,它将返回被等待的已被删除的节点 - * - * @return 超时信息 {@link AsyncTimeout} - * @throws InterruptedException 异常 + * Removes and returns the node at the head of the list, waiting for it to time out if necessary. + * This returns {@link #head} if there was no node at the head of the list when starting, and + * there continues to be no node after waiting {@code IDLE_TIMEOUT_NANOS}. It returns null if a + * new node was inserted while waiting. Otherwise this returns the node being waited on that has + * been removed. */ static AsyncTimeout awaitTimeout() throws InterruptedException { - // 获取下一个符合条件的节点 + // Get the next eligible node. AsyncTimeout node = head.next; - // 队列为空。等待,直到某物进入队列或空闲超时过期 - if (null == node) { + + // The queue is empty. Wait until either something is enqueued or the idle timeout elapses. + if (node == null) { long startNanos = System.nanoTime(); AsyncTimeout.class.wait(IDLE_TIMEOUT_MILLIS); - return null == head.next && (System.nanoTime() - startNanos) >= IDLE_TIMEOUT_NANOS - ? head // 空闲超时过期 - : null; // 情况发生了变化 + return head.next == null && (System.nanoTime() - startNanos) >= IDLE_TIMEOUT_NANOS + ? head // The idle timeout elapsed. + : null; // The situation has changed. } long waitNanos = node.remainingNanos(System.nanoTime()); - // 队伍的头还没有超时。等待 + // The head of the queue hasn't timed out yet. Await that. if (waitNanos > 0) { - // 由于我们的工作时间是十亿分之一秒,所以等待变得很复杂,但是API需要两个参数(millis, nanos) + // Waiting is made complicated by the fact that we work in nanoseconds, + // but the API wants (millis, nanos) in two arguments. long waitMillis = waitNanos / 1000000L; waitNanos -= (waitMillis * 1000000L); AsyncTimeout.class.wait(waitMillis, (int) waitNanos); return null; } - // 队列的头已经超时了,删除它 + + // The head of the queue has timed out. Remove it. head.next = node.next; node.next = null; return node; } - /** - * 调用者应该在执行超时工作之前调用{@link #enter},然后调用{@link #exit} - * {@link #exit}的返回值指示是否触发超时。注意,对{@link #timedOut}的调用是异步的, - * 可以在{@link #exit}之后调用 - */ public final void enter() { if (inQueue) throw new IllegalStateException("Unbalanced enter/exit"); long timeoutNanos = timeoutNanos(); boolean hasDeadline = hasDeadline(); if (timeoutNanos == 0 && !hasDeadline) { - // 没有暂停和截止日期?别去排队 - return; + return; // No timeout and no deadline? Don't bother with the queue. } inQueue = true; scheduleTimeout(this, timeoutNanos, hasDeadline); } /** - * 如果超时发生,则返回true - * - * @return the true/false + * Returns true if the timeout occurred. */ public final boolean exit() { if (!inQueue) return false; @@ -192,36 +193,32 @@ public final boolean exit() { } /** - * 返回在超时之前剩余的时间量 - * 如果超时已经过去,并且应该立即发生超时,则该值为负 - * - * @param now 当前时间 - * @return + * Returns the amount of time left until the time out. This will be negative if the timeout has + * elapsed and the timeout should occur immediately. */ private long remainingNanos(long now) { return timeoutAt - now; } /** - * 当对{@link #enter()}和{@link #exit()}的调用之间的时间超过超时时,watchdog线程将调用它 + * Invoked by the watchdog thread when the time between calls to {@link #enter()} and {@link + * #exit()} has exceeded the timeout. */ protected void timedOut() { } /** - * 返回一个委托给{@code sink}的新缓冲接收器,使用它来实现超时。 - * 如果{@link #timedOut}被覆盖以中断{@code sink}的当前操作,那么这是最有效的 - * - * @param sink 缓冲接收器 - * @return 新缓冲接收器 + * Returns a new sink that delegates to {@code sink}, using this to implement timeouts. This works + * best if {@link #timedOut} is overridden to interrupt {@code sink}'s current operation. */ public final Sink sink(final Sink sink) { return new Sink() { @Override public void write(Buffer source, long byteCount) throws IOException { IoKit.checkOffsetAndCount(source.size, 0, byteCount); + while (byteCount > 0L) { - // 计算要写入的字节数。这个循环保证我们在一个段边界上分割 + // Count how many bytes to write. This loop guarantees we split on a segment boundary. long toWrite = 0L; for (Segment s = source.head; toWrite < TIMEOUT_WRITE_SIZE; s = s.next) { int segmentSize = s.limit - s.pos; @@ -231,7 +228,8 @@ public void write(Buffer source, long byteCount) throws IOException { break; } } - // 发出一个写。只有这个部分会超时 + + // Emit one write. Only this section is subject to the timeout. boolean throwOnTimeout = false; enter(); try { @@ -281,17 +279,14 @@ public Timeout timeout() { @Override public String toString() { - return "Awaits.sink(" + sink + ")"; + return "AsyncTimeout.sink(" + sink + ")"; } }; } /** - * 返回一个委托给{@code source}的新源,使用它来实现超时。 - * 如果{@link #timedOut}被覆盖以中断{@code sink}的当前操作,那么这是最有效的 - * - * @param source 源 - * @return 新源 + * Returns a new source that delegates to {@code source}, using this to implement timeouts. This + * works best if {@link #timedOut} is overridden to interrupt {@code sink}'s current operation. */ public final Source source(final Source source) { return new Source() { @@ -331,17 +326,14 @@ public Timeout timeout() { @Override public String toString() { - return "Awaits.source(" + source + ")"; + return "AsyncTimeout.source(" + source + ")"; } }; } /** - * 如果{@code throwOnTimeout}为{@code true}且发生超时,则抛出IOException。 - * 有关抛出的异常类型,请参见{@link #newTimeoutException(java.io.IOException)} - * - * @param throwOnTimeout 超时时间 - * @throws IOException 异常 + * Throws an IOException if {@code throwOnTimeout} is {@code true} and a timeout occurred. See + * {@link #newTimeoutException(java.io.IOException)} for the type of exception thrown. */ final void exit(boolean throwOnTimeout) throws IOException { boolean timedOut = exit(); @@ -349,27 +341,23 @@ final void exit(boolean throwOnTimeout) throws IOException { } /** - * 如果超时,则返回{@code cause}或{@code cause}引起的IOException。 - * 有关返回的异常类型,请参见{@link #newTimeoutException(java.io.IOException)} - * - * @param cause 异常 - * @return 异常信息 + * Returns either {@code cause} or an IOException that's caused by {@code cause} if a timeout + * occurred. See {@link #newTimeoutException(java.io.IOException)} for the type of exception + * returned. */ - final IOException exit(IOException cause) { + final IOException exit(IOException cause) throws IOException { if (!exit()) return cause; return newTimeoutException(cause); } /** - * 返回{@link IOException}表示超时。默认情况下,该方法返回{@link java.io.InterruptedIOException}。 - * 如果{@code cause}非空,则将其设置为返回异常的原因 - * - * @param cause 异常 - * @return 异常信息 + * Returns an {@link IOException} to represent a timeout. By default this method returns {@link + * java.io.InterruptedIOException}. If {@code cause} is non-null it is set as the cause of the + * returned exception. */ protected IOException newTimeoutException(IOException cause) { InterruptedIOException e = new InterruptedIOException("timeout"); - if (null != cause) { + if (cause != null) { e.initCause(cause); } return e; @@ -377,7 +365,7 @@ protected IOException newTimeoutException(IOException cause) { private static final class Watchdog extends Thread { Watchdog() { - super("IoKit.Watchdog"); + super("Okio Watchdog"); setDaemon(true); } @@ -388,16 +376,18 @@ public void run() { synchronized (AsyncTimeout.class) { timedOut = awaitTimeout(); - if (null == timedOut) { - continue; - } + // Didn't find a node to interrupt. Try again. + if (timedOut == null) continue; + // The queue is completely empty. Let this thread exit and let another watchdog thread + // get created on the next call to scheduleTimeout(). if (timedOut == head) { head = null; return; } } + // Close the timed out node. timedOut.timedOut(); } catch (InterruptedException ignored) { } diff --git a/bus-core/src/main/java/org/aoju/bus/core/io/timout/Timeout.java b/bus-core/src/main/java/org/aoju/bus/core/io/timout/Timeout.java old mode 100755 new mode 100644 index 7a55adaaf5..757e1e5272 --- a/bus-core/src/main/java/org/aoju/bus/core/io/timout/Timeout.java +++ b/bus-core/src/main/java/org/aoju/bus/core/io/timout/Timeout.java @@ -73,46 +73,46 @@ public void throwIfReached() { public Timeout() { } + static long minTimeout(long aNanos, long bNanos) { + if (aNanos == 0L) return bNanos; + if (bNanos == 0L) return aNanos; + if (aNanos < bNanos) return aNanos; + return bNanos; + } + /** - * @param timeout long - * @param unit TimeUnit - * @return timeout - *

* Wait at most {@code timeout} time before aborting an operation. Using a * per-operation timeout means that as long as forward progress is being made, * no sequence of operations will fail. + * *

If {@code timeout == 0}, operations will run indefinitely. (Operating * system timeouts may still apply.) */ public Timeout timeout(long timeout, TimeUnit unit) { - if (timeout < 0) { - throw new IllegalArgumentException("timeout < 0: " + timeout); - } - if (null == unit) { - throw new IllegalArgumentException("unit == null"); - } + if (timeout < 0) throw new IllegalArgumentException("timeout < 0: " + timeout); + if (unit == null) throw new IllegalArgumentException("unit == null"); this.timeoutNanos = unit.toNanos(timeout); return this; } /** - * @return the timeout in nanoseconds, or {@code 0} for no timeout. + * Returns the timeout in nanoseconds, or {@code 0} for no timeout. */ public long timeoutNanos() { return timeoutNanos; } /** - * @return hasDeadline true if a deadline is enabled. + * Returns true if a deadline is enabled. */ public boolean hasDeadline() { return hasDeadline; } /** - * @return deadlineNanoTime * Returns the {@linkplain System#nanoTime() nano time} when the deadline will * be reached. + * * @throws IllegalStateException if no deadline is set. */ public long deadlineNanoTime() { @@ -121,8 +121,6 @@ public long deadlineNanoTime() { } /** - * @param deadlineNanoTime long - * @return timeout * Sets the {@linkplain System#nanoTime() nano time} when the deadline will be * reached. All operations must complete before this time. Use a deadline to * set a maximum bound on the time spent on a sequence of operations. @@ -134,23 +132,16 @@ public Timeout deadlineNanoTime(long deadlineNanoTime) { } /** - * @param duration long - * @param unit TimeUnit - * @return timeout * Set a deadline of now plus {@code duration} time. */ public final Timeout deadline(long duration, TimeUnit unit) { - if (duration <= 0) { - throw new IllegalArgumentException("duration <= 0: " + duration); - } - if (null == unit) { - throw new IllegalArgumentException("unit == null"); - } + if (duration <= 0) throw new IllegalArgumentException("duration <= 0: " + duration); + if (unit == null) throw new IllegalArgumentException("unit == null"); return deadlineNanoTime(System.nanoTime() + unit.toNanos(duration)); } /** - * @return this Clears the timeout. Operating system timeouts may still apply. + * Clears the timeout. Operating system timeouts may still apply. */ public Timeout clearTimeout() { this.timeoutNanos = 0; @@ -158,7 +149,7 @@ public Timeout clearTimeout() { } /** - * @return this Clears the deadline. + * Clears the deadline. */ public Timeout clearDeadline() { this.hasDeadline = false; @@ -169,12 +160,10 @@ public Timeout clearDeadline() { * Throws an {@link InterruptedIOException} if the deadline has been reached or if the current * thread has been interrupted. This method doesn't detect timeouts; that should be implemented to * asynchronously abort an in-progress operation. - * - * @throws IOException 抛出异常 */ public void throwIfReached() throws IOException { if (Thread.interrupted()) { - Thread.currentThread().interrupt(); + Thread.currentThread().interrupt(); // Retain interrupted status. throw new InterruptedIOException("interrupted"); } @@ -184,10 +173,40 @@ public void throwIfReached() throws IOException { } /** - * @param monitor Waits on {@code monitor} until it is notified. Throws {@link InterruptedIOException} if either - * the thread is interrupted or if this timeout elapses before {@code monitor} is notified. The - * caller must be synchronized on {@code monitor}. - * @throws InterruptedIOException 抛出异常 + * Waits on {@code monitor} until it is notified. Throws {@link InterruptedIOException} if either + * the thread is interrupted or if this timeout elapses before {@code monitor} is notified. The + * caller must be synchronized on {@code monitor}. + * + *

Here's a sample class that uses {@code waitUntilNotified()} to await a specific state. Note + * that the call is made within a loop to avoid unnecessary waiting and to mitigate spurious + * notifications.

{@code
+     *
+     *   class Dice {
+     *     Random random = new Random();
+     *     int latestTotal;
+     *
+     *     public synchronized void roll() {
+     *       latestTotal = 2 + random.nextInt(6) + random.nextInt(6);
+     *       System.out.println("Rolled " + latestTotal);
+     *       notifyAll();
+     *     }
+     *
+     *     public void rollAtFixedRate(int period, TimeUnit timeUnit) {
+     *       Executors.newScheduledThreadPool(0).scheduleAtFixedRate(new Runnable() {
+     *         public void run() {
+     *           roll();
+     *          }
+     *       }, 0, period, timeUnit);
+     *     }
+     *
+     *     public synchronized void awaitTotal(Timeout timeout, int total)
+     *         throws InterruptedIOException {
+     *       while (latestTotal != total) {
+     *         timeout.waitUntilNotified(this);
+     *       }
+     *     }
+     *   }
+     * }
*/ public final void waitUntilNotified(Object monitor) throws InterruptedIOException { try { @@ -195,10 +214,11 @@ public final void waitUntilNotified(Object monitor) throws InterruptedIOExceptio long timeoutNanos = timeoutNanos(); if (!hasDeadline && timeoutNanos == 0L) { - monitor.wait(); + monitor.wait(); // There is no timeout: wait forever. return; } + // Compute how long we'll wait. long waitNanos; long start = System.nanoTime(); if (hasDeadline && timeoutNanos != 0) { @@ -210,6 +230,7 @@ public final void waitUntilNotified(Object monitor) throws InterruptedIOExceptio waitNanos = timeoutNanos; } + // Attempt to wait that long. This will break out early if the monitor is notified. long elapsedNanos = 0L; if (waitNanos > 0L) { long waitMillis = waitNanos / 1000000L; @@ -217,6 +238,7 @@ public final void waitUntilNotified(Object monitor) throws InterruptedIOExceptio elapsedNanos = System.nanoTime() - start; } + // Throw if the timeout elapsed before the monitor was notified. if (elapsedNanos >= waitNanos) { throw new InterruptedIOException("timeout"); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/key/HashID.java b/bus-core/src/main/java/org/aoju/bus/core/key/HashID.java deleted file mode 100755 index a6e9a3c3ed..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/key/HashID.java +++ /dev/null @@ -1,457 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.key; - -import org.aoju.bus.core.lang.Normal; -import org.aoju.bus.core.lang.Symbol; -import org.aoju.bus.core.toolkit.NetKit; -import org.aoju.bus.core.toolkit.RuntimeKit; - -import java.util.ArrayList; -import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Hashids用于从数字(如YouTube和Bitly)生成短散列, - * 数据库id,将它们用作忘记密码散列、邀请码、存储碎片号 - * - * @author Kimi Liu - * @since Java 17+ - */ -public class HashID { - - /** - * Max number that can be encoded with Hashids. - */ - public static final long MAX_NUMBER = 9007199254740992L; - - private static final String DEFAULT_SEPS = "cfhistuCFHISTU"; - private static final String DEFAULT_SALT = Normal.EMPTY; - - private static final Pattern PATTERN = Pattern.compile("[\\w\\W]{1,12}"); - - private static final int DEFAULT_MIN_HASH_LENGTH = 0; - private static final int MIN_ALPHABET_LENGTH = Normal._16; - private static final double SEP_DIV = 3.5; - private static final int GUARD_DIV = 12; - - private final String salt; - private final int minHashLength; - private final String alphabet; - private final String seps; - private final String guards; - - public HashID() { - this(DEFAULT_SALT); - } - - public HashID(String salt) { - this(salt, 0); - } - - public HashID(String salt, int minHashLength) { - this(salt, minHashLength, Normal.UPPER_LOWER_NUMBER); - } - - public HashID(String salt, int minHashLength, String alphabet) { - this.salt = null != salt ? salt : DEFAULT_SALT; - this.minHashLength = minHashLength > 0 ? minHashLength : DEFAULT_MIN_HASH_LENGTH; - - final StringBuilder uniqueAlphabet = new StringBuilder(); - for (int i = 0; i < alphabet.length(); i++) { - if (uniqueAlphabet.indexOf(String.valueOf(alphabet.charAt(i))) == -1) { - uniqueAlphabet.append(alphabet.charAt(i)); - } - } - - alphabet = uniqueAlphabet.toString(); - - if (alphabet.length() < MIN_ALPHABET_LENGTH) { - throw new IllegalArgumentException( - "alphabet must contain at least " + MIN_ALPHABET_LENGTH + " unique characters"); - } - - if (alphabet.contains(Symbol.SPACE)) { - throw new IllegalArgumentException("alphabet cannot contains spaces"); - } - - // seps should contain only characters present in alphabet; - // alphabet should not contains seps - String seps = DEFAULT_SEPS; - for (int i = 0; i < seps.length(); i++) { - final int j = alphabet.indexOf(seps.charAt(i)); - if (j == -1) { - seps = seps.substring(0, i) + Symbol.SPACE + seps.substring(i + 1); - } else { - alphabet = alphabet.substring(0, j) + Symbol.SPACE + alphabet.substring(j + 1); - } - } - - alphabet = alphabet.replaceAll("\\s+", Normal.EMPTY); - seps = seps.replaceAll("\\s+", Normal.EMPTY); - seps = consistentShuffle(seps, this.salt); - - if ((seps.isEmpty()) || (((float) alphabet.length() / seps.length()) > SEP_DIV)) { - int seps_len = (int) Math.ceil(alphabet.length() / SEP_DIV); - - if (seps_len == 1) { - seps_len++; - } - - if (seps_len > seps.length()) { - final int diff = seps_len - seps.length(); - seps += alphabet.substring(0, diff); - alphabet = alphabet.substring(diff); - } else { - seps = seps.substring(0, seps_len); - } - } - - alphabet = consistentShuffle(alphabet, this.salt); - // use double to round up - final int guardCount = (int) Math.ceil((double) alphabet.length() / GUARD_DIV); - - String guards; - if (alphabet.length() < 3) { - guards = seps.substring(0, guardCount); - seps = seps.substring(guardCount); - } else { - guards = alphabet.substring(0, guardCount); - alphabet = alphabet.substring(guardCount); - } - this.guards = guards; - this.alphabet = alphabet; - this.seps = seps; - } - - public static int checkedCast(long value) { - final int result = (int) value; - if (result != value) { - // don't use checkArgument here, to avoid boxing - throw new IllegalArgumentException("Out of range: " + value); - } - return result; - } - - private static String consistentShuffle(String alphabet, String salt) { - if (salt.length() <= 0) { - return alphabet; - } - - int asc_val, j; - final char[] tmpArr = alphabet.toCharArray(); - for (int i = tmpArr.length - 1, v = 0, p = 0; i > 0; i--, v++) { - v %= salt.length(); - asc_val = salt.charAt(v); - p += asc_val; - j = (asc_val + v + p) % i; - final char tmp = tmpArr[j]; - tmpArr[j] = tmpArr[i]; - tmpArr[i] = tmp; - } - - return new String(tmpArr); - } - - private static String hash(long input, String alphabet) { - String hash = Normal.EMPTY; - final int alphabetLen = alphabet.length(); - - do { - final int index = (int) (input % alphabetLen); - if (index >= 0 && index < alphabet.length()) { - hash = alphabet.charAt(index) + hash; - } - input /= alphabetLen; - } while (input > 0); - - return hash; - } - - private static Long unhash(String input, String alphabet) { - long number = 0, pos; - - for (int i = 0; i < input.length(); i++) { - pos = alphabet.indexOf(input.charAt(i)); - number = number * alphabet.length() + pos; - } - - return number; - } - - /** - * 获取数据中心ID,依赖于本地网卡MAC地址 - *

- * 此算法来自于mybatis-plus#Sequence - *

- * - * @param maxDatacenterId 最大的中心ID - * @return 数据中心ID - */ - public static long getDataCenterId(long maxDatacenterId) { - long id = 1L; - final byte[] mac = NetKit.getLocalHardwareAddress(); - if (null != mac) { - id = ((0x000000FF & (long) mac[mac.length - 2]) - | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6; - id = id % (maxDatacenterId + 1); - } - - return id; - } - - /** - * 获取机器ID,使用进程ID配合数据中心ID生成 - * 依赖于本进程ID或进程名的Hash值 - *

- * 此算法来自于mybatis-plus#Sequence - *

- * - * @param datacenterId 数据中心ID - * @param maxWorkerId 最大的机器节点ID - * @return the long - */ - public static long getWorkerId(long datacenterId, long maxWorkerId) { - final StringBuilder mpid = new StringBuilder(); - mpid.append(datacenterId); - try { - mpid.append(RuntimeKit.getPid()); - } catch (InstantiationError igonre) { - } - // MAC + PID 的 hashcode 获取16个低位 - return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1); - } - - /** - * Encrypt numbers to string - * - * @param numbers the numbers to encrypt - * @return the encrypt string - */ - public String encode(long... numbers) { - if (numbers.length == 0) { - return Normal.EMPTY; - } - - for (final long number : numbers) { - if (number < 0) { - return Normal.EMPTY; - } - if (number > MAX_NUMBER) { - throw new IllegalArgumentException("number can not be greater than " + MAX_NUMBER + "L"); - } - } - return this._encode(numbers); - } - - /** - * Decrypt string to numbers - * - * @param hash the encrypt string - * @return decryped numbers - */ - public long[] decode(String hash) { - if (hash.isEmpty()) { - return Normal.EMPTY_LONG_ARRAY; - } - - String validChars = this.alphabet + this.guards + this.seps; - for (int i = 0; i < hash.length(); i++) { - if (validChars.indexOf(hash.charAt(i)) == -1) { - return Normal.EMPTY_LONG_ARRAY; - } - } - - return this._decode(hash, this.alphabet); - } - - /** - * Encrypt hexa to string - * - * @param hexa the hexa to encrypt - * @return the encrypt string - */ - public String encodeHex(String hexa) { - if (!hexa.matches("^[0-9a-fA-F]+$")) { - return Normal.EMPTY; - } - - final List matched = new ArrayList<>(); - final Matcher matcher = PATTERN.matcher(hexa); - - while (matcher.find()) { - matched.add(Long.parseLong(Symbol.ONE + matcher.group(), Normal._16)); - } - - // conversion - final long[] result = new long[matched.size()]; - for (int i = 0; i < matched.size(); i++) { - result[i] = matched.get(i); - } - - return this.encode(result); - } - - /** - * Decrypt string to numbers - * - * @param hash the encrypt string - * @return decryped numbers - */ - public String decodeHex(String hash) { - final StringBuilder result = new StringBuilder(); - final long[] numbers = this.decode(hash); - - for (final long number : numbers) { - result.append(Long.toHexString(number).substring(1)); - } - - return result.toString(); - } - - private String _encode(long... numbers) { - long numberHashInt = 0; - for (int i = 0; i < numbers.length; i++) { - numberHashInt += (numbers[i] % (i + 100)); - } - String alphabet = this.alphabet; - final char ret = alphabet.charAt((int) (numberHashInt % alphabet.length())); - - long num; - long sepsIndex, guardIndex; - String buffer; - final StringBuilder ret_strB = new StringBuilder(this.minHashLength); - ret_strB.append(ret); - char guard; - - for (int i = 0; i < numbers.length; i++) { - num = numbers[i]; - buffer = ret + this.salt + alphabet; - - alphabet = consistentShuffle(alphabet, buffer.substring(0, alphabet.length())); - final String last = hash(num, alphabet); - - ret_strB.append(last); - - if (i + 1 < numbers.length) { - if (last.length() > 0) { - num %= (last.charAt(0) + i); - sepsIndex = (int) (num % this.seps.length()); - } else { - sepsIndex = 0; - } - ret_strB.append(this.seps.charAt((int) sepsIndex)); - } - } - - String ret_str = ret_strB.toString(); - if (ret_str.length() < this.minHashLength) { - guardIndex = (numberHashInt + (ret_str.charAt(0))) % this.guards.length(); - guard = this.guards.charAt((int) guardIndex); - - ret_str = guard + ret_str; - - if (ret_str.length() < this.minHashLength) { - guardIndex = (numberHashInt + (ret_str.charAt(2))) % this.guards.length(); - guard = this.guards.charAt((int) guardIndex); - - ret_str += guard; - } - } - - final int halfLen = alphabet.length() / 2; - while (ret_str.length() < this.minHashLength) { - alphabet = consistentShuffle(alphabet, alphabet); - ret_str = alphabet.substring(halfLen) + ret_str + alphabet.substring(0, halfLen); - final int excess = ret_str.length() - this.minHashLength; - if (excess > 0) { - final int start_pos = excess / 2; - ret_str = ret_str.substring(start_pos, start_pos + this.minHashLength); - } - } - - return ret_str; - } - - private long[] _decode(String hash, String alphabet) { - final List ret = new ArrayList<>(); - String shuffle = alphabet; - int i = 0; - final String regexp = Symbol.BRACKET_LEFT + this.guards + Symbol.BRACKET_RIGHT; - String hashBreakdown = hash.replaceAll(regexp, Symbol.SPACE); - String[] hashArray = hashBreakdown.split(Symbol.SPACE); - - if (hashArray.length == 3 || hashArray.length == 2) { - i = 1; - } - - if (hashArray.length > 0) { - hashBreakdown = hashArray[i]; - if (!hashBreakdown.isEmpty()) { - final char lottery = hashBreakdown.charAt(0); - - hashBreakdown = hashBreakdown.substring(1); - hashBreakdown = hashBreakdown.replaceAll(Symbol.BRACKET_LEFT + this.seps + Symbol.BRACKET_RIGHT, Symbol.SPACE); - hashArray = hashBreakdown.split(Symbol.SPACE); - - String subHash, buffer; - for (final String aHashArray : hashArray) { - subHash = aHashArray; - buffer = lottery + this.salt + shuffle; - shuffle = consistentShuffle(shuffle, buffer.substring(0, shuffle.length())); - ret.add(unhash(subHash, shuffle)); - } - } - } - - // transform from List to long[] - long[] arr = new long[ret.size()]; - for (int k = 0; k < arr.length; k++) { - arr[k] = ret.get(k); - } - - if (!this.encode(arr).equals(hash)) { - arr = Normal.EMPTY_LONG_ARRAY; - } - - return arr; - } - - /** - * Get Hashid algorithm version. - * - * @return id algorithm version implemented. - */ - public String getVersion() { - return "1.0.0"; - } - - public String getSalt() { - return salt; - } - -} diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/Console.java b/bus-core/src/main/java/org/aoju/bus/core/lang/Console.java index 1e8988a982..ad4d0541bd 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/Console.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/Console.java @@ -454,7 +454,7 @@ public static class Table { * * @return Table */ - public static Table create() { + public static Table of() { return new Table(); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/Enums.java b/bus-core/src/main/java/org/aoju/bus/core/lang/Enums.java index 9110511c0d..aa986ae4ef 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/Enums.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/Enums.java @@ -10,65 +10,65 @@ */ public interface Enums> extends Serializable { - String name(); + String name(); - /** - * 在中文语境下,多数时间枚举会配合一个中文说明 - * - * @return enum名 - */ - default String text() { - return name(); - } + /** + * 在中文语境下,多数时间枚举会配合一个中文说明 + * + * @return enum名 + */ + default String text() { + return name(); + } - int intVal(); + int intVal(); - /** - * 获取所有枚举对象 - * - * @return 枚举对象数组 - */ - default E[] items() { - return (E[]) this.getClass().getEnumConstants(); - } + /** + * 获取所有枚举对象 + * + * @return 枚举对象数组 + */ + default E[] items() { + return (E[]) this.getClass().getEnumConstants(); + } - /** - * 通过int类型值查找兄弟其他枚举 - * - * @param intVal int值 - * @return Enum - */ - default E from(final Integer intVal) { - if (intVal == null) { - return null; - } - final E[] vs = items(); - for (final E enumItem : vs) { - if (enumItem.intVal() == intVal) { - return enumItem; - } - } - return null; - } + /** + * 通过int类型值查找兄弟其他枚举 + * + * @param intVal int值 + * @return Enum + */ + default E from(final Integer intVal) { + if (intVal == null) { + return null; + } + final E[] vs = items(); + for (final E enumItem : vs) { + if (enumItem.intVal() == intVal) { + return enumItem; + } + } + return null; + } - /** - * 通过String类型的值转换,根据实现可以用name/text - * - * @param strVal String值 - * @return Enum - */ - default E from(final String strVal) { - if (strVal == null) { - return null; - } - final E[] vs = items(); - for (final E enumItem : vs) { - if (strVal.equalsIgnoreCase(enumItem.name())) { - return enumItem; - } - } - return null; - } + /** + * 通过String类型的值转换,根据实现可以用name/text + * + * @param strVal String值 + * @return Enum + */ + default E from(final String strVal) { + if (strVal == null) { + return null; + } + final E[] vs = items(); + for (final E enumItem : vs) { + if (strVal.equalsIgnoreCase(enumItem.name())) { + return enumItem; + } + } + return null; + } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/FileType.java b/bus-core/src/main/java/org/aoju/bus/core/lang/FileType.java index 9995f38bbd..c842b69f40 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/FileType.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/FileType.java @@ -75,6 +75,11 @@ public class FileType { public static final String TYPE_PPTX = ".pptx"; public static final String TYPE_PPS = ".pps"; public static final String TYPE_PPSX = ".ppsx"; + /** + * XML格式 + */ + public static final String TYPE_XML = ".xml"; + /** * psd格式,Photoshop的专用格式Photoshop */ @@ -970,7 +975,6 @@ public static String getType(File file) throws InternalException { */ public static String getType(InputStream in, String filename) { String typeName = getType(in); - if (null == typeName) { // 未成功识别类型,扩展名辅助识别 typeName = FileKit.getSuffix(filename); @@ -981,6 +985,8 @@ public static String getType(InputStream in, String filename) { typeName = "doc"; } else if ("msi".equalsIgnoreCase(extName)) { typeName = "msi"; + } else if ("ppt".equalsIgnoreCase(extName)) { + typeName = "ppt"; } } else if ("zip".equals(typeName)) { // zip可能为docx、xlsx、pptx、jar、war、ofd等格式,扩展名辅助判断 diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/Http.java b/bus-core/src/main/java/org/aoju/bus/core/lang/Http.java index a4316c5573..3d553c0519 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/Http.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/Http.java @@ -187,6 +187,10 @@ public class Http { * Supports RFC 5246: TLS version 1.2 ; may support other versions */ public static final String TLS_V_12 = "TLSv1.2"; + /** + * Supports RFC 5246: TLS version 1.3 ; may support other versions + */ + public static final String TLS_V_13 = "TLSv1.3"; /** * Supports SSL version 2 or later; may support other versions */ diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/Normal.java b/bus-core/src/main/java/org/aoju/bus/core/lang/Normal.java index fb598be888..8ac65a3e5e 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/Normal.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/Normal.java @@ -358,15 +358,25 @@ public class Normal { public static final String LOWER_NUMBER = LOWER + NUMBER; /** - * 字符串: 大小字母 + * 字符串: 大写字母 + 小写字母 */ public static final String UPPER_LOWER = UPPER + LOWER; + /** + * 字符串: 小写字母 + 大写字母 + */ + public static final String LOWER_UPPER = LOWER + UPPER; + /** * 字符串: 大小字母数字 */ public static final String UPPER_LOWER_NUMBER = UPPER_LOWER + NUMBER; + /** + * 字符串: 大小字母数字 + */ + public static final String LOWER_UPPER_NUMBER = LOWER_UPPER + NUMBER; + /** * 七色 */ diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/RegEx.java b/bus-core/src/main/java/org/aoju/bus/core/lang/RegEx.java index 2cc90d0dc7..c510bee186 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/RegEx.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/RegEx.java @@ -116,6 +116,12 @@ public class RegEx { public static final String GROUP_VAR_PATTERN = "\\$(\\d+)"; public static final Pattern GROUP_VAR = Pattern.compile(GROUP_VAR_PATTERN); + /** + * 快速区分IP地址和主机名 + */ + public static final String IP_ADDRESS_PATTERN = "([0-9a-fA-F]*:[0-9a-fA-F:.]*)|([\\d.]+)"; + public static final Pattern IP_ADDRESS = Pattern.compile(IP_ADDRESS_PATTERN); + /** * IP v4 */ diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/Weighing.java b/bus-core/src/main/java/org/aoju/bus/core/lang/Weighing.java index a1fe804960..4c6166abe0 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/Weighing.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/Weighing.java @@ -106,7 +106,7 @@ public Weighing(WeightObject[] weightObjs) { * @param 对象 * @return {@link Weighing} */ - public static Weighing create() { + public static Weighing of() { return new Weighing<>(); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/ansi/Ansi8BitColor.java b/bus-core/src/main/java/org/aoju/bus/core/lang/ansi/Ansi8BitColor.java index 1134123995..f8c8d5b0e3 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/ansi/Ansi8BitColor.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/ansi/Ansi8BitColor.java @@ -44,6 +44,21 @@ public final class Ansi8BitColor implements AnsiElement { private static final String PREFIX_FORE = "38;5;"; private static final String PREFIX_BACK = "48;5;"; + private final String prefix; + private final int code; + + /** + * 构造 + * + * @param prefix 前缀 + * @param code 颜色代码(0-255) + * @throws IllegalArgumentException 颜色代码不在0~255范围内 + */ + private Ansi8BitColor(String prefix, int code) { + Assert.isTrue(code >= 0 && code <= 255, "Code must be between 0 and 255"); + this.prefix = prefix; + this.code = code; + } /** * 前景色ANSI颜色实例 @@ -65,22 +80,6 @@ public static Ansi8BitColor background(int code) { return new Ansi8BitColor(PREFIX_BACK, code); } - private final String prefix; - private final int code; - - /** - * 构造 - * - * @param prefix 前缀 - * @param code 颜色代码(0-255) - * @throws IllegalArgumentException 颜色代码不在0~255范围内 - */ - private Ansi8BitColor(String prefix, int code) { - Assert.isTrue(code >= 0 && code <= 255, "Code must be between 0 and 255"); - this.prefix = prefix; - this.code = code; - } - /** * 获取颜色代码(0-255) * diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/range/Range.java b/bus-core/src/main/java/org/aoju/bus/core/lang/range/Range.java index fa459940a0..e255fceceb 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/range/Range.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/range/Range.java @@ -44,211 +44,211 @@ */ public class Range implements Iterable, Iterator, Serializable { - private static final long serialVersionUID = 1L; - /** - * 起始对象 - */ - private final T start; - /** - * 结束对象 - */ - private final T end; - /** - * 步进 - */ - private final Stepper stepper; - /** - * 是否包含第一个元素 - */ - private final boolean includeStart; - /** - * 是否包含最后一个元素 - */ - private final boolean includeEnd; - /** - * 锁保证线程安全 - */ - private Lock lock = new ReentrantLock(); - /** - * 下一个对象 - */ - private T next; - /** - * 索引 - */ - private int index = 0; - - /** - * 构造 - * - * @param start 起始对象(包括) - * @param stepper 步进 - */ - public Range(final T start, final Stepper stepper) { - this(start, null, stepper); - } - - /** - * 构造 - * - * @param start 起始对象(包含) - * @param end 结束对象(包含) - * @param stepper 步进 - */ - public Range(final T start, final T end, final Stepper stepper) { - this(start, end, stepper, true, true); - } - - /** - * 构造 - * - * @param start 起始对象 - * @param end 结束对象 - * @param stepper 步进 - * @param isIncludeStart 是否包含第一个元素 - * @param isIncludeEnd 是否包含最后一个元素 - */ - public Range(final T start, final T end, final Stepper stepper, final boolean isIncludeStart, final boolean isIncludeEnd) { - Assert.notNull(start, "First element must be not null!"); - this.start = start; - this.end = end; - this.stepper = stepper; - this.next = safeStep(this.start); - this.includeStart = isIncludeStart; - this.includeEnd = isIncludeEnd; - } - - /** - * 禁用锁,调用此方法后不再使用锁保护 - * - * @return this - */ - public Range disableLock() { - this.lock = new AtomicNoLock(); - return this; - } - - @Override - public boolean hasNext() { - lock.lock(); - try { - if (0 == this.index && this.includeStart) { - return true; - } - if (null == this.next) { - return false; - } else if (false == includeEnd && this.next.equals(this.end)) { - return false; - } - } finally { - lock.unlock(); - } - return true; - } - - @Override - public T next() { - lock.lock(); - try { - if (false == this.hasNext()) { - throw new NoSuchElementException("Has no next range!"); - } - return nextUncheck(); - } finally { - lock.unlock(); - } - } - - /** - * 获取下一个元素,并将下下个元素准备好 - */ - private T nextUncheck() { - final T current; - if (0 == this.index) { - current = start; - if (false == this.includeStart) { - // 获取下一组元素 - index++; - return nextUncheck(); - } - } else { - current = next; - this.next = safeStep(this.next); - } - - index++; - return current; - } - - /** - * 不抛异常的获取下一步进的元素,如果获取失败返回{@code null} - * - * @param base 上一个元素 - * @return 下一步进 - */ - private T safeStep(final T base) { - final int index = this.index; - T next = null; - try { - next = stepper.step(base, this.end, index); - } catch (final Exception e) { - // ignore - } - - return next; - } - - @Override - public void remove() { - throw new UnsupportedOperationException("Can not remove ranged element!"); - } - - @Override - public Iterator iterator() { - return this; - } - - /** - * 重置Range - * - * @return this - */ - public Range reset() { - lock.lock(); - try { - this.index = 0; - this.next = safeStep(this.start); - } finally { - lock.unlock(); - } - return this; - } - - /** - * 步进接口,此接口用于实现如何对一个对象按照指定步进增加步进 - * 步进接口可以定义以下逻辑: - * - *
-	 * 1、步进规则,即对象如何做步进
-	 * 2、步进大小,通过实现此接口,在实现类中定义一个对象属性,可灵活定义步进大小
-	 * 3、限制range个数,通过实现此接口,在实现类中定义一个对象属性,可灵活定义limit,限制range个数
-	 * 
- * - * @param 需要增加步进的对象 - */ - @FunctionalInterface - public interface Stepper { - /** - * 增加步进 - * 增加步进后的返回值如果为{@code null}则表示步进结束 - * 用户需根据end参数自行定义边界,当达到边界时返回null表示结束,否则Range中边界对象无效,会导致无限循环 - * - * @param current 上一次增加步进后的基础对象 - * @param end 结束对象 - * @param index 当前索引(步进到第几个元素),从0开始计数 - * @return 增加步进后的对象 - */ - T step(T current, T end, int index); - } + private static final long serialVersionUID = 1L; + /** + * 起始对象 + */ + private final T start; + /** + * 结束对象 + */ + private final T end; + /** + * 步进 + */ + private final Stepper stepper; + /** + * 是否包含第一个元素 + */ + private final boolean includeStart; + /** + * 是否包含最后一个元素 + */ + private final boolean includeEnd; + /** + * 锁保证线程安全 + */ + private Lock lock = new ReentrantLock(); + /** + * 下一个对象 + */ + private T next; + /** + * 索引 + */ + private int index = 0; + + /** + * 构造 + * + * @param start 起始对象(包括) + * @param stepper 步进 + */ + public Range(final T start, final Stepper stepper) { + this(start, null, stepper); + } + + /** + * 构造 + * + * @param start 起始对象(包含) + * @param end 结束对象(包含) + * @param stepper 步进 + */ + public Range(final T start, final T end, final Stepper stepper) { + this(start, end, stepper, true, true); + } + + /** + * 构造 + * + * @param start 起始对象 + * @param end 结束对象 + * @param stepper 步进 + * @param isIncludeStart 是否包含第一个元素 + * @param isIncludeEnd 是否包含最后一个元素 + */ + public Range(final T start, final T end, final Stepper stepper, final boolean isIncludeStart, final boolean isIncludeEnd) { + Assert.notNull(start, "First element must be not null!"); + this.start = start; + this.end = end; + this.stepper = stepper; + this.next = safeStep(this.start); + this.includeStart = isIncludeStart; + this.includeEnd = isIncludeEnd; + } + + /** + * 禁用锁,调用此方法后不再使用锁保护 + * + * @return this + */ + public Range disableLock() { + this.lock = new AtomicNoLock(); + return this; + } + + @Override + public boolean hasNext() { + lock.lock(); + try { + if (0 == this.index && this.includeStart) { + return true; + } + if (null == this.next) { + return false; + } else if (false == includeEnd && this.next.equals(this.end)) { + return false; + } + } finally { + lock.unlock(); + } + return true; + } + + @Override + public T next() { + lock.lock(); + try { + if (false == this.hasNext()) { + throw new NoSuchElementException("Has no next range!"); + } + return nextUncheck(); + } finally { + lock.unlock(); + } + } + + /** + * 获取下一个元素,并将下下个元素准备好 + */ + private T nextUncheck() { + final T current; + if (0 == this.index) { + current = start; + if (false == this.includeStart) { + // 获取下一组元素 + index++; + return nextUncheck(); + } + } else { + current = next; + this.next = safeStep(this.next); + } + + index++; + return current; + } + + /** + * 不抛异常的获取下一步进的元素,如果获取失败返回{@code null} + * + * @param base 上一个元素 + * @return 下一步进 + */ + private T safeStep(final T base) { + final int index = this.index; + T next = null; + try { + next = stepper.step(base, this.end, index); + } catch (final Exception e) { + // ignore + } + + return next; + } + + @Override + public void remove() { + throw new UnsupportedOperationException("Can not remove ranged element!"); + } + + @Override + public Iterator iterator() { + return this; + } + + /** + * 重置Range + * + * @return this + */ + public Range reset() { + lock.lock(); + try { + this.index = 0; + this.next = safeStep(this.start); + } finally { + lock.unlock(); + } + return this; + } + + /** + * 步进接口,此接口用于实现如何对一个对象按照指定步进增加步进 + * 步进接口可以定义以下逻辑: + * + *
+     * 1、步进规则,即对象如何做步进
+     * 2、步进大小,通过实现此接口,在实现类中定义一个对象属性,可灵活定义步进大小
+     * 3、限制range个数,通过实现此接口,在实现类中定义一个对象属性,可灵活定义limit,限制range个数
+     * 
+ * + * @param 需要增加步进的对象 + */ + @FunctionalInterface + public interface Stepper { + /** + * 增加步进 + * 增加步进后的返回值如果为{@code null}则表示步进结束 + * 用户需根据end参数自行定义边界,当达到边界时返回null表示结束,否则Range中边界对象无效,会导致无限循环 + * + * @param current 上一次增加步进后的基础对象 + * @param end 结束对象 + * @param index 当前索引(步进到第几个元素),从0开始计数 + * @return 增加步进后的对象 + */ + T step(T current, T end, int index); + } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/reflect/MethodHandle.java b/bus-core/src/main/java/org/aoju/bus/core/lang/reflect/MethodHandle.java index 14ca611dd0..b7f9a94d4d 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/reflect/MethodHandle.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/reflect/MethodHandle.java @@ -149,7 +149,7 @@ public static java.lang.invoke.MethodHandle findConstructor(Class callerClass * * * @param 返回结果类型 - * @param object 接口的子对象或代理对象 + * @param object 接口的子对象或代理对象 * @param methodName 方法名称 * @param args 参数 * @return 结果 @@ -195,7 +195,7 @@ public static T invoke(Object object, Method method, Object... args) { * * * @param 返回结果类型 - * @param object 接口的子对象或代理对象 + * @param object 接口的子对象或代理对象 * @param method 方法 * @param args 参数 * @return 结果 @@ -215,14 +215,14 @@ public static T invokeSpecial(Object object, Method method, Object... args) * } * * Duck duck = (Duck) Proxy.newProxyInstance( - * ClassLoaderUtil.getClassLoader(), + * ClassKit.getClassLoader(), * new Class[] { Duck.class }, * MethodHandle::invoke); * * * @param 返回结果类型 * @param isSpecial 是否为特殊方法(private、static等) - * @param object 接口的子对象或代理对象 + * @param object 接口的子对象或代理对象 * @param method 方法 * @param args 参数 * @return 结果 diff --git a/bus-core/src/main/java/org/aoju/bus/core/lang/tree/TreeBuilder.java b/bus-core/src/main/java/org/aoju/bus/core/lang/tree/TreeBuilder.java index 2df953c9cf..225b44f707 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/lang/tree/TreeBuilder.java +++ b/bus-core/src/main/java/org/aoju/bus/core/lang/tree/TreeBuilder.java @@ -32,7 +32,6 @@ import org.aoju.bus.core.toolkit.MapKit; import org.aoju.bus.core.toolkit.ObjectKit; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -58,10 +57,10 @@ public class TreeBuilder implements Builder> { * @param rootId 根节点ID * @param config 配置 */ - public TreeBuilder(E rootId, NodeConfig config) { + public TreeBuilder(final E rootId, final NodeConfig config) { root = new Tree<>(config); root.setId(rootId); - this.idTreeMap = new HashMap<>(); + this.idTreeMap = new LinkedHashMap<>(); } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/loader/JarLoaders.java b/bus-core/src/main/java/org/aoju/bus/core/loader/JarLoaders.java index 2eef054598..58964fb722 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/loader/JarLoaders.java +++ b/bus-core/src/main/java/org/aoju/bus/core/loader/JarLoaders.java @@ -110,7 +110,6 @@ public static void loadJar(URLClassLoader loader, File jarFile) throws InternalE try { final Method method = ClassKit.getDeclaredMethod(URLClassLoader.class, "addURL", URL.class); if (null != method) { - method.setAccessible(true); final List jars = loopJar(jarFile); for (File jar : jars) { ReflectKit.invoke(loader, method, jar.toURI().toURL()); diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/AbstractCollValueMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/AbstractCollValueMap.java index 090fa54812..c13b51cadb 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/map/AbstractCollValueMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/AbstractCollValueMap.java @@ -25,123 +25,166 @@ ********************************************************************************/ package org.aoju.bus.core.map; +import org.aoju.bus.core.lang.Optional; import org.aoju.bus.core.toolkit.CollKit; +import org.aoju.bus.core.toolkit.ObjectKit; import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.Supplier; +import java.util.stream.Collectors; /** * 值作为集合的Map实现,通过调用putValue可以在相同key时加入多个值,多个值用集合表示 * * @param 键类型 * @param 值类型 - * @param 集合类型 * @author Kimi Liu * @since Java 17+ */ -public abstract class AbstractCollValueMap> extends MapWrapper { +public abstract class AbstractCollValueMap extends MapWrapper> implements MultiValueMap { /** * 默认集合初始大小 */ protected static final int DEFAULT_COLLECTION_INITIAL_CAPACITY = 3; + private static final long serialVersionUID = 1L; /** - * 构造 + * 基于{@link HashMap}构造一个多值映射集合 */ public AbstractCollValueMap() { - this(DEFAULT_INITIAL_CAPACITY); + super(new HashMap<>(16)); } /** - * 构造 + * 基于{@link HashMap}构造一个多值映射集合 * - * @param initialCapacity 初始大小 + * @param map 提供初始数据的集合 */ - public AbstractCollValueMap(int initialCapacity) { - this(initialCapacity, DEFAULT_LOAD_FACTOR); + public AbstractCollValueMap(Map> map) { + super(new HashMap<>(map)); } /** - * 构造 + * 使用{@code mapFactory}创建的集合构造一个多值映射Map集合 * - * @param m Map + * @param mapFactory 生成集合的工厂方法 */ - public AbstractCollValueMap(Map m) { - this(DEFAULT_LOAD_FACTOR, m); + public AbstractCollValueMap(Supplier>> mapFactory) { + super(mapFactory); } /** - * 构造 + * 将集合中的全部元素对追加到指定键对应的值集合中,效果等同于: + *
{@code
+     * coll.forEach(t -> map.putValue(key, t))
+     * }
* - * @param loadFactor 加载因子 - * @param m Map + * @param key 键 + * @param coll 待添加的值集合 + * @return 是否成功添加 */ - public AbstractCollValueMap(float loadFactor, Map m) { - this(m.size(), loadFactor); - this.putAll(m); + @Override + public boolean putAllValues(K key, Collection coll) { + if (ObjectKit.isNull(coll)) { + return false; + } + return super.computeIfAbsent(key, k -> createCollection()) + .addAll(coll); } /** - * 构造 + * 向指定键对应的值集合追加值,效果等同于: + *
{@code
+     * map.computeIfAbsent(key, k -> new Collection()).add(value)
+     * }
* - * @param initialCapacity 初始大小 - * @param loadFactor 加载因子 + * @param key 键 + * @param value 值 + * @return 是否成功添加 */ - public AbstractCollValueMap(int initialCapacity, float loadFactor) { - super(new HashMap<>(initialCapacity, loadFactor)); + @Override + public boolean putValue(K key, V value) { + return super.computeIfAbsent(key, k -> createCollection()) + .add(value); } /** - * 放入所有value + * 将值从指定键下的值集合中删除 * - * @param m valueMap + * @param key 键 + * @param value 值 + * @return 是否成功删除 */ - public void putAllValues(Map> m) { - if (null != m) { - m.forEach((key, valueColl) -> { - if (null != valueColl) { - valueColl.forEach((value) -> putValue(key, value)); - } - }); - } + @Override + public boolean removeValue(K key, V value) { + return Optional.ofNullable(super.get(key)) + .map(t -> t.remove(value)) + .orElse(false); } /** - * 放入Value - * 如果键对应值列表有值,加入,否则创建一个新列表后加入 + * 将一批值从指定键下的值集合中删除 * - * @param key 键 - * @param value 值 + * @param key 键 + * @param values 值 + * @return 是否成功删除 */ - public void putValue(K key, V value) { - C collection = this.get(key); - if (null == collection) { - collection = createCollection(); - this.put(key, collection); + @Override + public boolean removeAllValues(K key, Collection values) { + if (CollKit.isEmpty(values)) { + return false; } - collection.add(value); + Collection coll = get(key); + return ObjectKit.isNotNull(coll) && coll.removeAll(values); } /** - * 获取值 + * 根据条件过滤所有值集合中的值,并以新值生成新的值集合,新集合中的值集合类型与当前实例的默认值集合类型保持一致 * - * @param key 键 - * @param index 第几个值的索引,越界返回null - * @return 值或null + * @param filter 判断方法 + * @return 当前实例 + */ + @Override + public MultiValueMap filterAllValues(BiPredicate filter) { + entrySet().forEach(e -> { + K k = e.getKey(); + Collection coll = e.getValue().stream() + .filter(v -> filter.test(k, v)) + .collect(Collectors.toCollection(this::createCollection)); + e.setValue(coll); + }); + return this; + } + + /** + * 根据条件替换所有值集合中的值,并以新值生成新的值集合,新集合中的值集合类型与当前实例的默认值集合类型保持一致 + * + * @param operate 替换方法 + * @return 当前实例 */ - public V get(K key, int index) { - final Collection collection = get(key); - return CollKit.get(collection, index); + @Override + public MultiValueMap replaceAllValues(BiFunction operate) { + entrySet().forEach(e -> { + K k = e.getKey(); + Collection coll = e.getValue().stream() + .map(v -> operate.apply(k, v)) + .collect(Collectors.toCollection(this::createCollection)); + e.setValue(coll); + }); + return this; } /** - * 创建集合 + * 创建集合
* 此方法用于创建在putValue后追加值所在的集合,子类实现此方法创建不同类型的集合 * * @return {@link Collection} */ - protected abstract C createCollection(); + public abstract Collection createCollection(); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/AbstractEntry.java b/bus-core/src/main/java/org/aoju/bus/core/map/AbstractEntry.java index 4c2ed843c7..03074a671b 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/map/AbstractEntry.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/AbstractEntry.java @@ -42,12 +42,12 @@ public abstract class AbstractEntry implements Map.Entry { @Override - public V setValue(V value) { + public V setValue(final V value) { throw new UnsupportedOperationException("Entry is read only."); } @Override - public boolean equals(Object object) { + public boolean equals(final Object object) { if (object instanceof Map.Entry) { final Map.Entry that = (Map.Entry) object; return ObjectKit.equals(this.getKey(), that.getKey()) diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/AbstractTable.java b/bus-core/src/main/java/org/aoju/bus/core/map/AbstractTable.java index e120793714..d1754eeacf 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/map/AbstractTable.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/AbstractTable.java @@ -56,11 +56,11 @@ public abstract class AbstractTable implements Table { private Set> cellSet; @Override - public boolean equals(Object object) { - if (object == this) { + public boolean equals(final Object obj) { + if (obj == this) { return true; - } else if (object instanceof Table) { - final Table that = (Table) object; + } else if (obj instanceof Table) { + final Table that = (Table) obj; return this.cellSet().equals(that.cellSet()); } else { return false; @@ -79,13 +79,13 @@ public String toString() { @Override public Collection values() { - Collection result = values; + final Collection result = values; return (result == null) ? values = new Values() : result; } @Override public Set> cellSet() { - Set> result = cellSet; + final Set> result = cellSet; return (result == null) ? cellSet = new CellSet() : result; } @@ -109,7 +109,7 @@ private static class SimpleCell implements Cell, Serializable private final C columnKey; private final V value; - SimpleCell(R rowKey, C columnKey, V value) { + SimpleCell(final R rowKey, final C columnKey, final V value) { this.rowKey = rowKey; this.columnKey = columnKey; this.value = value; @@ -131,7 +131,7 @@ public V getValue() { } @Override - public boolean equals(Object object) { + public boolean equals(final Object object) { if (object == this) { return true; } @@ -163,7 +163,7 @@ public Iterator iterator() { } @Override - public boolean contains(Object o) { + public boolean contains(final Object o) { return containsValue((V) o); } @@ -180,10 +180,10 @@ public int size() { private class CellSet extends AbstractSet> { @Override - public boolean contains(Object o) { + public boolean contains(final Object o) { if (o instanceof Cell) { final Cell cell = (Cell) o; - Map row = getRow(cell.getRowKey()); + final Map row = getRow(cell.getRowKey()); if (null != row) { return ObjectKit.equals(row.get(cell.getColumnKey()), cell.getValue()); } @@ -192,7 +192,7 @@ public boolean contains(Object o) { } @Override - public boolean remove(Object o) { + public boolean remove(final Object o) { if (contains(o)) { final Cell cell = (Cell) o; AbstractTable.this.remove(cell.getRowKey(), cell.getColumnKey()); @@ -206,7 +206,7 @@ public void clear() { } @Override - public Iterator> iterator() { + public Iterator> iterator() { return new CellIterator(); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/CamelCaseLinkedMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/CamelCaseLinkedMap.java old mode 100755 new mode 100644 index 3f3181e961..38b9167c47 --- a/bus-core/src/main/java/org/aoju/bus/core/map/CamelCaseLinkedMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/CamelCaseLinkedMap.java @@ -39,6 +39,8 @@ */ public class CamelCaseLinkedMap extends CamelCaseMap { + private static final long serialVersionUID = 1L; + /** * 构造 */ @@ -51,7 +53,7 @@ public CamelCaseLinkedMap() { * * @param initialCapacity 初始大小 */ - public CamelCaseLinkedMap(int initialCapacity) { + public CamelCaseLinkedMap(final int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } @@ -60,7 +62,7 @@ public CamelCaseLinkedMap(int initialCapacity) { * * @param m Map */ - public CamelCaseLinkedMap(Map m) { + public CamelCaseLinkedMap(final Map m) { this(DEFAULT_LOAD_FACTOR, m); } @@ -70,7 +72,7 @@ public CamelCaseLinkedMap(Map m) { * @param loadFactor 加载因子 * @param map 数据会被默认拷贝到一个新的LinkedHashMap中 */ - public CamelCaseLinkedMap(float loadFactor, Map map) { + public CamelCaseLinkedMap(final float loadFactor, final Map map) { this(map.size(), loadFactor); this.putAll(map); } @@ -81,7 +83,7 @@ public CamelCaseLinkedMap(float loadFactor, Map map) { * @param initialCapacity 初始大小 * @param loadFactor 加载因子 */ - public CamelCaseLinkedMap(int initialCapacity, float loadFactor) { + public CamelCaseLinkedMap(final int initialCapacity, final float loadFactor) { super(new LinkedHashMap<>(initialCapacity, loadFactor)); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/CamelCaseMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/CamelCaseMap.java old mode 100755 new mode 100644 index a56208e2b2..f412ee4cfa --- a/bus-core/src/main/java/org/aoju/bus/core/map/CamelCaseMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/CamelCaseMap.java @@ -43,6 +43,8 @@ */ public class CamelCaseMap extends FuncKeyMap { + private static final long serialVersionUID = 1L; + /** * 构造 */ @@ -55,17 +57,17 @@ public CamelCaseMap() { * * @param initialCapacity 初始大小 */ - public CamelCaseMap(int initialCapacity) { + public CamelCaseMap(final int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * 构造 * - * @param map Map + * @param m Map */ - public CamelCaseMap(Map map) { - this(DEFAULT_LOAD_FACTOR, map); + public CamelCaseMap(final Map m) { + this(DEFAULT_LOAD_FACTOR, m); } /** @@ -74,7 +76,7 @@ public CamelCaseMap(Map map) { * @param loadFactor 加载因子 * @param map 初始Map,数据会被默认拷贝到一个新的HashMap中 */ - public CamelCaseMap(float loadFactor, Map map) { + public CamelCaseMap(final float loadFactor, final Map map) { this(map.size(), loadFactor); this.putAll(map); } @@ -85,8 +87,8 @@ public CamelCaseMap(float loadFactor, Map map) { * @param initialCapacity 初始大小 * @param loadFactor 加载因子 */ - public CamelCaseMap(int initialCapacity, float loadFactor) { - this(MapBuilder.create(new HashMap<>(initialCapacity, loadFactor))); + public CamelCaseMap(final int initialCapacity, final float loadFactor) { + this(MapBuilder.of(new HashMap<>(initialCapacity, loadFactor))); } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveLinkedMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveLinkedMap.java old mode 100755 new mode 100644 index 2fcb2e026c..baed18401c --- a/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveLinkedMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveLinkedMap.java @@ -39,6 +39,8 @@ */ public class CaseInsensitiveLinkedMap extends CaseInsensitiveMap { + private static final long serialVersionUID = 1L; + /** * 构造 */ @@ -51,17 +53,17 @@ public CaseInsensitiveLinkedMap() { * * @param initialCapacity 初始大小 */ - public CaseInsensitiveLinkedMap(int initialCapacity) { + public CaseInsensitiveLinkedMap(final int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } /** * 构造 * - * @param map Map + * @param m Map */ - public CaseInsensitiveLinkedMap(Map map) { - this(DEFAULT_LOAD_FACTOR, map); + public CaseInsensitiveLinkedMap(final Map m) { + this(DEFAULT_LOAD_FACTOR, m); } /** @@ -70,7 +72,7 @@ public CaseInsensitiveLinkedMap(Map map) { * @param loadFactor 加载因子 * @param map Map */ - public CaseInsensitiveLinkedMap(float loadFactor, Map map) { + public CaseInsensitiveLinkedMap(final float loadFactor, final Map map) { this(map.size(), loadFactor); this.putAll(map); } @@ -81,7 +83,7 @@ public CaseInsensitiveLinkedMap(float loadFactor, Map * @param initialCapacity 初始大小 * @param loadFactor 加载因子 */ - public CaseInsensitiveLinkedMap(int initialCapacity, float loadFactor) { + public CaseInsensitiveLinkedMap(final int initialCapacity, final float loadFactor) { super(new LinkedHashMap<>(initialCapacity, loadFactor)); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveMap.java old mode 100755 new mode 100644 index ee3d3fd4d1..0f57ae611e --- a/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveMap.java @@ -41,6 +41,8 @@ */ public class CaseInsensitiveMap extends FuncKeyMap { + private static final long serialVersionUID = 1L; + /** * 构造 */ @@ -53,7 +55,7 @@ public CaseInsensitiveMap() { * * @param initialCapacity 初始大小 */ - public CaseInsensitiveMap(int initialCapacity) { + public CaseInsensitiveMap(final int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } @@ -63,7 +65,7 @@ public CaseInsensitiveMap(int initialCapacity) { * * @param map 被包装的自定义Map创建器 */ - public CaseInsensitiveMap(Map map) { + public CaseInsensitiveMap(final Map map) { this(DEFAULT_LOAD_FACTOR, map); } @@ -73,7 +75,7 @@ public CaseInsensitiveMap(Map map) { * @param loadFactor 加载因子 * @param map Map */ - public CaseInsensitiveMap(float loadFactor, Map map) { + public CaseInsensitiveMap(final float loadFactor, final Map map) { this(map.size(), loadFactor); this.putAll(map); } @@ -84,8 +86,8 @@ public CaseInsensitiveMap(float loadFactor, Map map) { * @param initialCapacity 初始大小 * @param loadFactor 加载因子 */ - public CaseInsensitiveMap(int initialCapacity, float loadFactor) { - this(MapBuilder.create(new HashMap<>(initialCapacity, loadFactor))); + public CaseInsensitiveMap(final int initialCapacity, final float loadFactor) { + this(MapBuilder.of(new HashMap<>(initialCapacity, loadFactor))); } /** @@ -94,7 +96,7 @@ public CaseInsensitiveMap(int initialCapacity, float loadFactor) { * * @param emptyMapBuilder 被包装的自定义Map创建器 */ - CaseInsensitiveMap(MapBuilder emptyMapBuilder) { + CaseInsensitiveMap(final MapBuilder emptyMapBuilder) { super(emptyMapBuilder.build(), (Function & Serializable) (key) -> { if (key instanceof CharSequence) { key = key.toString().toLowerCase(); diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveTreeMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveTreeMap.java index 530047f68e..3954ab5141 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveTreeMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/CaseInsensitiveTreeMap.java @@ -55,7 +55,7 @@ public CaseInsensitiveTreeMap() { * * @param map 初始Map */ - public CaseInsensitiveTreeMap(Map map) { + public CaseInsensitiveTreeMap(final Map map) { this(); this.putAll(map); } @@ -65,7 +65,7 @@ public CaseInsensitiveTreeMap(Map map) { * * @param map 初始Map,键值对会被复制到新的TreeMap中 */ - public CaseInsensitiveTreeMap(SortedMap map) { + public CaseInsensitiveTreeMap(final SortedMap map) { super(new TreeMap(map)); } @@ -74,7 +74,7 @@ public CaseInsensitiveTreeMap(SortedMap map) { * * @param comparator 比较器,{@code null}表示使用默认比较器 */ - public CaseInsensitiveTreeMap(Comparator comparator) { + public CaseInsensitiveTreeMap(final Comparator comparator) { super(new TreeMap<>(comparator)); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/CollectionValueMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/CollectionValueMap.java old mode 100755 new mode 100644 index d58e9441b3..dd6fd408ef --- a/bus-core/src/main/java/org/aoju/bus/core/map/CollectionValueMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/CollectionValueMap.java @@ -31,91 +31,64 @@ import java.util.Collection; import java.util.HashMap; import java.util.Map; +import java.util.function.Supplier; /** - * 值作为集合的Map实现,通过调用putValue可以在相同key时加入多个值,多个值用集合表示 + * {@link MultiValueMap}的通用实现,可视为值为{@link Collection}集合的{@link Map}集合 + * 构建时指定一个工厂方法用于生成原始的{@link Map}集合,然后再指定一个工厂方法用于生成自定义类型的值集合 + * 当调用{@link MultiValueMap}中格式为“putXXX”的方法时,将会为key创建值集合,并将key相同的值追加到集合中 * * @param 键类型 * @param 值类型 * @author Kimi Liu * @since Java 17+ */ -public class CollectionValueMap extends AbstractCollValueMap> { +public class CollectionValueMap extends AbstractCollValueMap { - private final XSupplier> collectionCreateFunc; + private static final long serialVersionUID = 1L; - /** - * 构造 - */ - public CollectionValueMap() { - this(DEFAULT_INITIAL_CAPACITY); - } - - /** - * 构造 - * - * @param initialCapacity 初始大小 - */ - public CollectionValueMap(int initialCapacity) { - this(initialCapacity, DEFAULT_LOAD_FACTOR); - } - - /** - * 构造 - * - * @param m Map - */ - public CollectionValueMap(Map> m) { - this(DEFAULT_LOAD_FACTOR, m); - } + private final XSupplier> collFactory; /** - * 构造 + * 创建一个多值映射集合,基于{@code mapFactory}与{@code collFactory}实现 * - * @param loadFactor 加载因子 - * @param m Map + * @param mapFactory 生成集合的工厂方法 + * @param collFactory 生成值集合的工厂方法 */ - public CollectionValueMap(float loadFactor, Map> m) { - this(loadFactor, m, ArrayList::new); + public CollectionValueMap(Supplier>> mapFactory, XSupplier> collFactory) { + super(mapFactory); + this.collFactory = collFactory; } /** - * 构造 + * 创建一个多值映射集合,默认基于{@link HashMap}与{@code collFactory}生成的集合实现 * - * @param initialCapacity 初始大小 - * @param loadFactor 加载因子 + * @param collFactory 生成值集合的工厂方法 */ - public CollectionValueMap(int initialCapacity, float loadFactor) { - this(initialCapacity, loadFactor, ArrayList::new); + public CollectionValueMap(XSupplier> collFactory) { + this.collFactory = collFactory; } /** - * 构造 - * - * @param loadFactor 加载因子 - * @param m Map - * @param collectionCreateFunc Map中值的集合创建函数 + * 创建一个多值映射集合,默认基于{@link HashMap}与{@link ArrayList}实现 */ - public CollectionValueMap(float loadFactor, Map> m, XSupplier> collectionCreateFunc) { - this(m.size(), loadFactor, collectionCreateFunc); - this.putAll(m); + public CollectionValueMap() { + this.collFactory = ArrayList::new; } /** - * 构造 + * 创建一个多值映射集合,默认基于{@link HashMap}与{@link ArrayList}实现 * - * @param initialCapacity 初始大小 - * @param loadFactor 加载因子 - * @param collectionCreateFunc Map中值的集合创建函数 + * @param map 提供数据的原始集合 */ - public CollectionValueMap(int initialCapacity, float loadFactor, XSupplier> collectionCreateFunc) { - super(new HashMap<>(initialCapacity, loadFactor)); - this.collectionCreateFunc = collectionCreateFunc; + public CollectionValueMap(Map> map) { + super(map); + this.collFactory = ArrayList::new; } @Override - protected Collection createCollection() { - return collectionCreateFunc.get(); + public Collection createCollection() { + return collFactory.get(); } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/CustomKeyMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/CustomKeyMap.java index b0638e9015..f070431c9a 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/map/CustomKeyMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/CustomKeyMap.java @@ -37,18 +37,20 @@ */ public abstract class CustomKeyMap extends TransitionMap { + private static final long serialVersionUID = 1L; + /** * 构造 * 通过传入一个Map从而确定Map的类型,子类需创建一个空的Map,而非传入一个已有Map,否则值可能会被修改 * * @param map 被包装的Map,必须为空Map,否则自定义key会无效 */ - public CustomKeyMap(Map map) { + public CustomKeyMap(final Map map) { super(map); } @Override - protected V customValue(Object value) { + protected V customValue(final Object value) { return (V) value; } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/Dictionary.java b/bus-core/src/main/java/org/aoju/bus/core/map/Dictionary.java index 258eceecf8..b921627c53 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/map/Dictionary.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/Dictionary.java @@ -25,12 +25,13 @@ ********************************************************************************/ package org.aoju.bus.core.map; -import org.aoju.bus.core.beans.PathExpression; +import org.aoju.bus.core.beans.BeanPath; import org.aoju.bus.core.beans.copier.CopyOptions; import org.aoju.bus.core.convert.Convert; import org.aoju.bus.core.exception.InternalException; import org.aoju.bus.core.getter.TypeGetter; import org.aoju.bus.core.lang.Assert; +import org.aoju.bus.core.lang.function.XFunction; import org.aoju.bus.core.lang.function.XSupplier; import org.aoju.bus.core.toolkit.BeanKit; import org.aoju.bus.core.toolkit.CollKit; @@ -47,9 +48,9 @@ */ public class Dictionary extends CustomKeyMap implements TypeGetter { - private static final long serialVersionUID = 1L; - private static final float DEFAULT_LOAD_FACTOR = 0.75f; - private static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; + static final float DEFAULT_LOAD_FACTOR = 0.75f; + static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; + private static final long serialVersionUID = 6135423866861206530L; /** * 是否大小写不敏感 @@ -150,11 +151,11 @@ public static Dictionary parse(final T bean) { */ @SafeVarargs public static Dictionary ofEntries(final Map.Entry... pairs) { - final Dictionary dict = of(); + final Dictionary dictionary = of(); for (final Map.Entry pair : pairs) { - dict.put(pair.getKey(), pair.getValue()); + dictionary.put(pair.getKey(), pair.getValue()); } - return dict; + return dictionary; } /** @@ -174,18 +175,18 @@ public static Dictionary ofEntries(final Map.Entry... pairs) { * @return this */ public static Dictionary ofKvs(final Object... keysAndValues) { - final Dictionary dict = of(); + final Dictionary dictionary = of(); String key = null; for (int i = 0; i < keysAndValues.length; i++) { - if (i % 2 == 0) { + if ((i & 1) == 0) { key = Convert.toString(keysAndValues[i]); } else { - dict.put(key, keysAndValues[i]); + dictionary.put(key, keysAndValues[i]); } } - return dict; + return dictionary; } @@ -268,7 +269,7 @@ public T toBeanIgnoreCase(final Class clazz) { * @return this */ public Dictionary parseBean(final T bean) { - Assert.notNull(bean, "Bean class must be not null"); + Assert.notNull(bean, "Bean must not be null"); this.putAll(BeanKit.beanToMap(bean)); return this; } @@ -284,14 +285,14 @@ public Dictionary parseBean(final T bean) { * @return this */ public Dictionary parseBean(final T bean, final boolean isToUnderlineCase, final boolean ignoreNullValue) { - Assert.notNull(bean, "Bean class must be not null"); + Assert.notNull(bean, "Bean must not be null"); this.putAll(BeanKit.beanToMap(bean, isToUnderlineCase, ignoreNullValue)); return this; } /** * 与给定实体对比并去除相同的部分 - * 此方法用于在更新操作时避免所有字段被更新,跳过不需要更新的字段 version from 2.0.0 + * 此方法用于在更新操作时避免所有字段被更新,跳过不需要更新的字段 * * @param 字典对象类型 * @param dict 字典对象 @@ -359,13 +360,25 @@ public Object getObject(final String key, final Object defaultValue) { return getOrDefault(key, defaultValue); } + /** + * 根据lambda的方法引用,获取 + * + * @param func 方法引用 + * @param

参数类型 + * @param 返回值类型 + * @return 获取表达式对应属性和返回的对象 + */ + public T get(final XFunction func) { + final LambdaKit.Info lambdaInfo = LambdaKit.resolve(func); + return get(lambdaInfo.getFieldName(), lambdaInfo.getReturnType()); + } + /** * 获得特定类型值 * * @param 值类型 * @param attr 字段名 * @return 字段值 - */ public T getBean(final String attr) { return (T) get(attr); @@ -390,10 +403,9 @@ public T getBean(final String attr) { * @param 目标类型 * @param expression 表达式 * @return the object - */ public T getByPath(final String expression) { - return (T) PathExpression.create(expression).get(this); + return (T) BeanPath.of(expression).get(this); } /** @@ -418,7 +430,6 @@ public T getByPath(final String expression) { * @param expression 表达式 * @param resultType 返回值类型 * @return the object - */ public T getByPath(final String expression, final Type resultType) { return Convert.convert(resultType, getByPath(expression)); @@ -446,7 +457,7 @@ protected String customKey(Object key) { * 实际使用时,可以使用getXXX的方法引用来完成键值对的赋值: *

      *     User user = GenericBuilder.of(User::new).with(User::setUsername, "bus").build();
-     *     Dictionary.create().setFields(user::getNickname, user::getUsername);
+     *     Dictionary.of().setFields(user::getNickname, user::getUsername);
      * 
* * @param fields lambda,不能为空 diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/DuplexingMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/DuplexingMap.java index dbde374a02..efa095ffa5 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/map/DuplexingMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/DuplexingMap.java @@ -51,12 +51,12 @@ public class DuplexingMap extends MapWrapper { * * @param raw 被包装的Map */ - public DuplexingMap(Map raw) { + public DuplexingMap(final Map raw) { super(raw); } @Override - public V put(K key, V value) { + public V put(final K key, final V value) { if (null != this.inverse) { this.inverse.put(value, key); } @@ -64,7 +64,7 @@ public V put(K key, V value) { } @Override - public void putAll(Map m) { + public void putAll(final Map m) { super.putAll(m); if (null != this.inverse) { m.forEach((key, value) -> this.inverse.put(value, key)); @@ -72,7 +72,7 @@ public void putAll(Map m) { } @Override - public V remove(Object key) { + public V remove(final Object key) { final V v = super.remove(key); if (null != this.inverse && null != v) { this.inverse.remove(v); @@ -81,7 +81,7 @@ public V remove(Object key) { } @Override - public boolean remove(Object key, Object value) { + public boolean remove(final Object key, final Object value) { return super.remove(key, value) && null != this.inverse && this.inverse.remove(value, key); } @@ -109,7 +109,7 @@ public Map getInverse() { * @param value 值 * @return 键 */ - public K getKey(V value) { + public K getKey(final V value) { return getInverse().get(value); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/FixedLinkedHashMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/FixedLinkedHashMap.java old mode 100755 new mode 100644 index e36517073d..bcdfc2a928 --- a/bus-core/src/main/java/org/aoju/bus/core/map/FixedLinkedHashMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/FixedLinkedHashMap.java @@ -40,6 +40,8 @@ */ public class FixedLinkedHashMap extends LinkedHashMap { + private static final long serialVersionUID = 1L; + /** * 容量,超过此容量自动删除末尾元素 */ @@ -54,7 +56,7 @@ public class FixedLinkedHashMap extends LinkedHashMap { * * @param capacity 容量,实际初始容量比容量大1 */ - public FixedLinkedHashMap(int capacity) { + public FixedLinkedHashMap(final int capacity) { super(capacity + 1, 1.0f, true); this.capacity = capacity; } @@ -73,7 +75,7 @@ public int getCapacity() { * * @param capacity 容量 */ - public void setCapacity(int capacity) { + public void setCapacity(final int capacity) { this.capacity = capacity; } @@ -87,7 +89,7 @@ public void setRemoveListener(final Consumer> removeListener) { } @Override - protected boolean removeEldestEntry(final java.util.Map.Entry eldest) { + protected boolean removeEldestEntry(final Map.Entry eldest) { // 当链表元素大于容量时,移除最老(最久未被使用)的元素 if (size() > this.capacity) { if (null != removeListener) { diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/ForestMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/ForestMap.java index fbbd7fd44f..2d74dc001a 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/map/ForestMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/ForestMap.java @@ -59,7 +59,7 @@ public interface ForestMap extends Map> { * @see #putNode(Object, Object) */ @Override - default TreeEntry put(K key, TreeEntry node) { + default TreeEntry put(final K key, final TreeEntry node) { return putNode(key, node.getValue()); } @@ -69,7 +69,7 @@ default TreeEntry put(K key, TreeEntry node) { * @param treeEntryMap 节点集合 */ @Override - default void putAll(Map> treeEntryMap) { + default void putAll(final Map> treeEntryMap) { if (CollKit.isEmpty(treeEntryMap)) { return; } @@ -83,28 +83,6 @@ default void putAll(Map> treeEntryMap) { }); } - /** - * 将指定节点从当前{@link Map}中删除 - *
    - *
  • 若存在父节点或子节点,则将其断开其与父节点或子节点的引用关系;
  • - *
  • - * 若同时存在父节点或子节点,则会在删除后将让子节点直接成为父节点的子节点,比如:
    - * 现有引用关系 a -> b -> c,删除 b 后,将有 a -> c - *
  • - *
- * - * @param key 节点的key - * @return 删除的节点,若key没有对应节点,则返回null - */ - @Override - TreeEntry remove(Object key); - - /** - * 将当前集合清空,并清除全部节点间的引用关系 - */ - @Override - void clear(); - /** * 批量添加节点 * @@ -115,7 +93,7 @@ default void putAll(Map> treeEntryMap) { * @param ignoreNullNode 是否获取到的key为null的子节点/父节点 */ default > void putAllNode( - C values, Function keyGenerator, Function parentKeyGenerator, boolean ignoreNullNode) { + final C values, final Function keyGenerator, final Function parentKeyGenerator, final boolean ignoreNullNode) { if (CollKit.isEmpty(values)) { return; } @@ -179,7 +157,7 @@ default > void putAllNode( * @param childKey 子节点的key * @param childValue 子节点的值 */ - default void putLinkedNodes(K parentKey, V parentValue, K childKey, V childValue) { + default void putLinkedNodes(final K parentKey, final V parentValue, final K childKey, final V childValue) { putNode(parentKey, parentValue); putNode(childKey, childValue); linkNodes(parentKey, childKey); @@ -204,7 +182,7 @@ default void putLinkedNodes(K parentKey, V parentValue, K childKey, V childValue * @param parentKey 父节点的key * @param childKey 子节点的key */ - default void linkNodes(K parentKey, K childKey) { + default void linkNodes(final K parentKey, final K childKey) { linkNodes(parentKey, childKey, null); } @@ -226,13 +204,13 @@ default void linkNodes(K parentKey, K childKey) { void unlinkNode(K parentKey, K childKey); /** - * 获取指定节点所在树结构的全部树节点
+ * 获取指定节点所在树结构的全部树节点 * 比如:存在 a -> b -> c 的关系,则输入 a/b/c 都将返回 a, b, c * * @param key 指定节点的key * @return 节点 */ - default Set> getTreeNodes(K key) { + default Set> getTreeNodes(final K key) { final TreeEntry target = get(key); if (ObjectKit.isNull(target)) { return Collections.emptySet(); @@ -243,26 +221,26 @@ default Set> getTreeNodes(K key) { } /** - * 获取以指定节点作为叶子节点的树结构,然后获取该树结构的根节点
+ * 获取以指定节点作为叶子节点的树结构,然后获取该树结构的根节点 * 比如:存在 a -> b -> c 的关系,则输入 a/b/c 都将返回 a * * @param key 指定节点的key * @return 节点 */ - default TreeEntry getRootNode(K key) { + default TreeEntry getRootNode(final K key) { return Optional.ofNullable(get(key)) .map(TreeEntry::getRoot) .orElse(null); } /** - * 获取指定节点的直接父节点
+ * 获取指定节点的直接父节点 * 比如:若存在 a -> b -> c 的关系,此时输入 a 将返回 null,输入 b 将返回 a,输入 c 将返回 b * * @param key 指定节点的key * @return 节点 */ - default TreeEntry getDeclaredParentNode(K key) { + default TreeEntry getDeclaredParentNode(final K key) { return Optional.ofNullable(get(key)) .map(TreeEntry::getDeclaredParent) .orElse(null); @@ -275,7 +253,7 @@ default TreeEntry getDeclaredParentNode(K key) { * @param parentKey 指定父节点key * @return 节点 */ - default TreeEntry getParentNode(K key, K parentKey) { + default TreeEntry getParentNode(final K key, final K parentKey) { return Optional.ofNullable(get(key)) .map(t -> t.getParent(parentKey)) .orElse(null); @@ -288,12 +266,24 @@ default TreeEntry getParentNode(K key, K parentKey) { * @param parentKey 指定父节点的key * @return 是否 */ - default boolean containsParentNode(K key, K parentKey) { + default boolean containsParentNode(final K key, final K parentKey) { return Optional.ofNullable(get(key)) .map(m -> m.containsParent(parentKey)) .orElse(false); } + /** + * 获取指定节点的值 + * + * @param key 节点的key + * @return 节点值,若节点不存在,或节点值为null都将返回null + */ + default V getNodeValue(final K key) { + return Optional.ofNullable(get(key)) + .map(TreeEntry::getValue) + .get(); + } + /** * 判断以该父节点作为根节点的树结构中是否具有指定子节点 * @@ -301,32 +291,20 @@ default boolean containsParentNode(K key, K parentKey) { * @param childKey 子节点 * @return 是否 */ - default boolean containsChildNode(K parentKey, K childKey) { + default boolean containsChildNode(final K parentKey, final K childKey) { return Optional.ofNullable(get(parentKey)) .map(m -> m.containsChild(childKey)) .orElse(false); } /** - * 获取指定节点的值 - * - * @param key 节点的key - * @return 节点值,若节点不存在,或节点值为null都将返回null - */ - default V getNodeValue(K key) { - return Optional.ofNullable(get(key)) - .map(TreeEntry::getValue) - .get(); - } - - /** - * 获取指定父节点直接关联的子节点
+ * 获取指定父节点直接关联的子节点 * 比如:若存在 a -> b -> c 的关系,此时输入 b 将返回 c,输入 a 将返回 b * * @param key key * @return 节点 */ - default Collection> getDeclaredChildNodes(K key) { + default Collection> getDeclaredChildNodes(final K key) { return Optional.ofNullable(get(key)) .map(TreeEntry::getDeclaredChildren) .map(Map::values) @@ -334,13 +312,13 @@ default Collection> getDeclaredChildNodes(K key) { } /** - * 获取指定父节点的全部子节点
+ * 获取指定父节点的全部子节点 * 比如:若存在 a -> b -> c 的关系,此时输入 b 将返回 c,输入 a 将返回 b,c * * @param key key * @return 该节点的全部子节点 */ - default Collection> getChildNodes(K key) { + default Collection> getChildNodes(final K key) { return Optional.ofNullable(get(key)) .map(TreeEntry::getChildren) .map(Map::values) diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/FuncKeyMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/FuncKeyMap.java old mode 100644 new mode 100755 index 9a78dfec81..fb5b5afe46 --- a/bus-core/src/main/java/org/aoju/bus/core/map/FuncKeyMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/FuncKeyMap.java @@ -49,7 +49,7 @@ public class FuncKeyMap extends CustomKeyMap { * @param emptyMap Map,提供的空map * @param keyFunc 自定义KEY的函数 */ - public FuncKeyMap(Map emptyMap, Function keyFunc) { + public FuncKeyMap(final Map emptyMap, final Function keyFunc) { super(emptyMap); this.keyFunc = keyFunc; } @@ -61,7 +61,7 @@ public FuncKeyMap(Map emptyMap, Function keyFunc) { * @return 驼峰Key */ @Override - protected K customKey(Object key) { + protected K customKey(final Object key) { if (null != this.keyFunc) { return keyFunc.apply(key); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/FuncMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/FuncMap.java index 17407c59af..eae49a086e 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/map/FuncMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/FuncMap.java @@ -52,7 +52,7 @@ public class FuncMap extends TransitionMap { * @param keyFunc 自定义KEY的函数 * @param valueFunc 自定义value函数 */ - public FuncMap(Supplier> mapFactory, Function keyFunc, Function valueFunc) { + public FuncMap(final Supplier> mapFactory, final Function keyFunc, final Function valueFunc) { this(mapFactory.get(), keyFunc, valueFunc); } @@ -64,7 +64,7 @@ public FuncMap(Supplier> mapFactory, Function keyFunc, Func * @param keyFunc 自定义KEY的函数 * @param valueFunc 自定义value函数 */ - public FuncMap(Map emptyMap, Function keyFunc, Function valueFunc) { + public FuncMap(final Map emptyMap, final Function keyFunc, final Function valueFunc) { super(emptyMap); this.keyFunc = keyFunc; this.valueFunc = valueFunc; @@ -77,7 +77,7 @@ public FuncMap(Map emptyMap, Function keyFunc, Function 节点类型 + * @author Kimi Liu + * @since Java 17+ + */ +public class GraphMap extends SetValueMap { + + /** + * 添加边 + * + * @param target1 节点 + * @param target2 节点 + */ + public void putEdge(final T target1, final T target2) { + this.putValue(target1, target2); + this.putValue(target2, target1); + } + + /** + * 是否存在边 + * + * @param target1 节点 + * @param target2 节点 + * @return 是否 + */ + public boolean containsEdge(final T target1, final T target2) { + return this.getValues(target1).contains(target2) + && this.getValues(target2).contains(target1); + } + + /** + * 移除边 + * + * @param target1 节点 + * @param target2 节点 + */ + public void removeEdge(final T target1, final T target2) { + this.removeValue(target1, target2); + this.removeValue(target2, target1); + } + + /** + * 移除节点,并删除该节点与其他节点之间连成的边 + * + * @param target 目标对象 + */ + public void removePoint(final T target) { + final Collection associatedPoints = this.remove(target); + if (CollKit.isNotEmpty(associatedPoints)) { + associatedPoints.forEach(p -> this.removeValue(p, target)); + } + } + + /** + * 两节点是否存在直接或间接的关联 + * + * @param target1 节点 + * @param target2 节点 + * @return 两节点是否存在关联 + */ + public boolean containsAssociation(final T target1, final T target2) { + if (!this.containsKey(target1) || !this.containsKey(target2)) { + return false; + } + final AtomicBoolean flag = new AtomicBoolean(false); + visitAssociatedPoints(target1, t -> { + if (Objects.equals(t, target2)) { + flag.set(true); + return true; + } + return false; + }); + return flag.get(); + } + + /** + * 按广度优先,获得节点的所有直接或间接关联的节点,节点默认按添加顺序排序 + * + * @param target 节点 + * @param includeTarget 是否包含查询节点 + * @return 节点的所有关联节点 + */ + public Collection getAssociatedPoints(final T target, final boolean includeTarget) { + final Set points = visitAssociatedPoints(target, t -> false); + if (!includeTarget) { + points.remove(target); + } + return points; + } + + /** + * 获取节点的邻接节点 + * + * @param target 节点 + * @return 邻接节点 + */ + public Collection getAdjacentPoints(final T target) { + return this.getValues(target); + } + + /** + * 按广度优先,访问节点的所有关联节点 + */ + private Set visitAssociatedPoints(final T key, final Predicate breaker) { + if (!this.containsKey(key)) { + return Collections.emptySet(); + } + final Set accessed = new HashSet<>(); + final Deque deque = new LinkedList<>(); + deque.add(key); + while (!deque.isEmpty()) { + // 访问节点 + final T t = deque.removeFirst(); + if (accessed.contains(t)) { + continue; + } + accessed.add(t); + // 若符合条件则中断循环 + if (breaker.test(t)) { + break; + } + // 获取邻接节点 + final Collection neighbours = this.getValues(t); + if (!neighbours.isEmpty()) { + deque.addAll(neighbours); + } + } + return accessed; + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/LinkedForestMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/LinkedForestMap.java index 3122548e16..fb804d8342 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/map/LinkedForestMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/LinkedForestMap.java @@ -198,7 +198,7 @@ public Collection> values() { * @return 集合 */ @Override - public Set>> entrySet() { + public Set>> entrySet() { return nodes.entrySet().stream() .map(this::wrap) .collect(Collectors.toSet()); @@ -207,7 +207,7 @@ public Set>> entrySet() { /** * 将{@link TreeEntryNode}包装为{@link EntryNodeWrapper} */ - private Entry> wrap(final Entry> nodeEntry) { + private Map.Entry> wrap(final Map.Entry> nodeEntry) { return new EntryNodeWrapper<>(nodeEntry.getValue()); } @@ -379,7 +379,7 @@ public static class TreeEntryNode implements TreeEntry { * @param parent 节点的父节点 * @param key 节点的key */ - public TreeEntryNode(TreeEntryNode parent, K key) { + public TreeEntryNode(final TreeEntryNode parent, final K key) { this(parent, key, null); } @@ -390,7 +390,7 @@ public TreeEntryNode(TreeEntryNode parent, K key) { * @param key 节点的key * @param value 节点的value */ - public TreeEntryNode(TreeEntryNode parent, K key, V value) { + public TreeEntryNode(final TreeEntryNode parent, final K key, final V value) { this.parent = parent; this.key = key; this.value = value; @@ -442,7 +442,7 @@ public V getValue() { * @return 节点的旧value */ @Override - public V setValue(V value) { + public V setValue(final V value) { final V oldVal = getValue(); this.value = value; return oldVal; @@ -457,7 +457,7 @@ public V setValue(V value) { * @return 遍历到的最后一个节点 */ TreeEntryNode traverseParentNodes( - boolean includeCurrent, Consumer> consumer, Predicate> breakTraverse) { + final boolean includeCurrent, final Consumer> consumer, Predicate> breakTraverse) { breakTraverse = ObjectKit.defaultIfNull(breakTraverse, n -> false); TreeEntryNode curr = includeCurrent ? this : this.parent; while (ObjectKit.isNotNull(curr)) { @@ -512,7 +512,7 @@ public TreeEntryNode getDeclaredParent() { * @return 指定父节点,当不存在时返回null */ @Override - public TreeEntryNode getParent(K key) { + public TreeEntryNode getParent(final K key) { return traverseParentNodes(false, p -> { }, p -> p.equalsKey(key)); } @@ -524,7 +524,7 @@ public TreeEntryNode getParent(K key) { * @param nodeConsumer 对节点的处理 */ @Override - public void forEachChild(boolean includeSelf, Consumer> nodeConsumer) { + public void forEachChild(final boolean includeSelf, final Consumer> nodeConsumer) { traverseChildNodes(includeSelf, (index, child) -> nodeConsumer.accept(child), null); } @@ -534,7 +534,7 @@ public void forEachChild(boolean includeSelf, Consumer> nodeCons * @param key 要比较的key * @return 是否key一致 */ - public boolean equalsKey(K key) { + public boolean equalsKey(final K key) { return ObjectKit.equals(getKey(), key); } @@ -547,7 +547,7 @@ public boolean equalsKey(K key) { * @return 遍历到的最后一个节点 */ TreeEntryNode traverseChildNodes( - boolean includeCurrent, BiConsumer> consumer, BiPredicate> breakTraverse) { + final boolean includeCurrent, final BiConsumer> consumer, BiPredicate> breakTraverse) { breakTraverse = ObjectKit.defaultIfNull(breakTraverse, (i, n) -> false); final Deque>> keyNodeDeque = CollKit.newLinkedList(CollKit.newArrayList(this)); boolean needProcess = includeCurrent; @@ -583,7 +583,7 @@ TreeEntryNode traverseChildNodes( * @param child 子节点 * @throws IllegalArgumentException 当要添加的子节点已经是其自身父节点时抛出 */ - void addChild(TreeEntryNode child) { + void addChild(final TreeEntryNode child) { if (containsChild(child.key)) { return; } @@ -611,7 +611,7 @@ void addChild(TreeEntryNode child) { * * @param key 子节点 */ - void removeDeclaredChild(K key) { + void removeDeclaredChild(final K key) { final TreeEntryNode child = children.get(key); if (ObjectKit.isNull(child)) { return; @@ -635,7 +635,7 @@ void removeDeclaredChild(K key) { * @return 节点 */ @Override - public TreeEntryNode getChild(K key) { + public TreeEntryNode getChild(final K key) { return traverseChildNodes(false, (i, c) -> { }, (i, c) -> c.equalsKey(key)); } @@ -679,7 +679,7 @@ void clear() { * @return 是否 */ @Override - public boolean equals(Object o) { + public boolean equals(final Object o) { if (this == o) { return true; } @@ -707,8 +707,8 @@ public int hashCode() { * @param value 复制的节点的值 * @return 节点 */ - TreeEntryNode copy(V value) { - TreeEntryNode copiedNode = new TreeEntryNode<>(this.parent, this.key, ObjectKit.defaultIfNull(value, this.value)); + TreeEntryNode copy(final V value) { + final TreeEntryNode copiedNode = new TreeEntryNode<>(this.parent, this.key, ObjectKit.defaultIfNull(value, this.value)); copiedNode.children.putAll(children); return copiedNode; } @@ -716,7 +716,7 @@ TreeEntryNode copy(V value) { } /** - * {@link Entry}包装类 + * {@link Map.Entry}包装类 * * @param key类型 * @param value类型 @@ -724,10 +724,10 @@ TreeEntryNode copy(V value) { * @see #entrySet() * @see #values() */ - public static class EntryNodeWrapper> implements Entry>, XWrapper { + public static class EntryNodeWrapper> implements Map.Entry>, XWrapper { private final N entryNode; - EntryNodeWrapper(N entryNode) { + EntryNodeWrapper(final N entryNode) { this.entryNode = entryNode; } @@ -742,7 +742,7 @@ public TreeEntry getValue() { } @Override - public TreeEntry setValue(TreeEntry value) { + public TreeEntry setValue(final TreeEntry value) { throw new UnsupportedOperationException(); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/ListValueMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/ListValueMap.java old mode 100755 new mode 100644 index 578f5c09d2..3b5becf449 --- a/bus-core/src/main/java/org/aoju/bus/core/map/ListValueMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/ListValueMap.java @@ -26,6 +26,7 @@ package org.aoju.bus.core.map; import java.util.*; +import java.util.function.Supplier; /** * 值作为集合List的Map实现,通过调用putValue可以在相同key时加入多个值,多个值用集合表示 @@ -35,56 +36,36 @@ * @author Kimi Liu * @since Java 17+ */ -public class ListValueMap extends AbstractCollValueMap> { +public class ListValueMap extends AbstractCollValueMap { - /** - * 构造 - */ - public ListValueMap() { - this(DEFAULT_INITIAL_CAPACITY); - } + private static final long serialVersionUID = 1L; /** - * 构造 - * - * @param initialCapacity 初始大小 + * 基于{@link HashMap}创建一个值为{@link List}的多值映射集合 */ - public ListValueMap(int initialCapacity) { - this(initialCapacity, DEFAULT_LOAD_FACTOR); - } - - /** - * 构造 - * - * @param m Map - */ - public ListValueMap(Map> m) { - this(DEFAULT_LOAD_FACTOR, m); + public ListValueMap() { } /** - * 构造 + * 基于{@link HashMap}创建一个值为{@link List}的多值映射集合 * - * @param loadFactor 加载因子 - * @param m Map + * @param map 提供数据的原始集合 */ - public ListValueMap(float loadFactor, Map> m) { - this(m.size(), loadFactor); - this.putAllValues(m); + public ListValueMap(final Map> map) { + super(map); } /** - * 构造 + * 基于{@code mapFactory}创建一个值为{@link List}的多值映射集合 * - * @param initialCapacity 初始大小 - * @param loadFactor 加载因子 + * @param mapFactory 创建集合的工厂反方 */ - public ListValueMap(int initialCapacity, float loadFactor) { - super(new HashMap<>(initialCapacity, loadFactor)); + public ListValueMap(final Supplier>> mapFactory) { + super(mapFactory); } @Override - protected List createCollection() { + public List createCollection() { return new ArrayList<>(DEFAULT_COLLECTION_INITIAL_CAPACITY); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/MapBuilder.java b/bus-core/src/main/java/org/aoju/bus/core/map/MapBuilder.java old mode 100755 new mode 100644 index 8f9e079f37..c397714272 --- a/bus-core/src/main/java/org/aoju/bus/core/map/MapBuilder.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/MapBuilder.java @@ -50,7 +50,7 @@ public class MapBuilder implements Builder> { * * @param map 要使用的Map实现类 */ - public MapBuilder(Map map) { + public MapBuilder(final Map map) { this.map = map; } @@ -59,10 +59,10 @@ public MapBuilder(Map map) { * * @param Key类型 * @param Value类型 - * @return MapBuilder + * @return this */ - public static MapBuilder create() { - return create(false); + public static MapBuilder of() { + return of(false); } /** @@ -71,10 +71,10 @@ public static MapBuilder create() { * @param Key类型 * @param Value类型 * @param isLinked true创建LinkedHashMap,false创建HashMap - * @return MapBuilder + * @return this */ - public static MapBuilder create(boolean isLinked) { - return create(MapKit.newHashMap(isLinked)); + public static MapBuilder of(final boolean isLinked) { + return of(MapKit.newHashMap(isLinked)); } /** @@ -83,9 +83,9 @@ public static MapBuilder create(boolean isLinked) { * @param Key类型 * @param Value类型 * @param map Map实体类 - * @return MapBuilder + * @return this */ - public static MapBuilder create(Map map) { + public static MapBuilder of(final Map map) { return new MapBuilder<>(map); } @@ -94,33 +94,22 @@ public static MapBuilder create(Map map) { * * @param k Key类型 * @param v Value类型 - * @return 当前类 + * @return this */ - public MapBuilder put(K k, V v) { + public MapBuilder put(final K k, final V v) { map.put(k, v); return this; } - /** - * 链式Map创建 - * - * @param k Key类型 - * @param supplier Value类型结果提供方 - * @return 当前类 - */ - public MapBuilder put(K k, Supplier supplier) { - return put(k, supplier.get()); - } - /** * 链式Map创建 * * @param condition put条件 * @param k Key类型 * @param v Value类型 - * @return 当前类 + * @return this */ - public MapBuilder put(boolean condition, K k, V v) { + public MapBuilder put(final boolean condition, final K k, final V v) { if (condition) { put(k, v); } @@ -133,11 +122,11 @@ public MapBuilder put(boolean condition, K k, V v) { * @param condition put条件 * @param k Key类型 * @param supplier Value类型结果提供方 - * @return 当前类 + * @return this */ - public MapBuilder put(boolean condition, K k, Supplier supplier) { + public MapBuilder put(final boolean condition, final K k, final Supplier supplier) { if (condition) { - put(k, supplier); + put(k, supplier.get()); } return this; } @@ -146,9 +135,9 @@ public MapBuilder put(boolean condition, K k, Supplier supplier) { * 链式Map创建 * * @param map 合并map - * @return 当前类 + * @return this */ - public MapBuilder putAll(Map map) { + public MapBuilder putAll(final Map map) { this.map.putAll(map); return this; } @@ -177,6 +166,7 @@ public Map map() { * * @return 创建后的map */ + @Override public Map build() { return map(); } @@ -188,7 +178,7 @@ public Map build() { * @param keyValueSeparator kv之间的连接符 * @return 连接字符串 */ - public String join(String separator, final String keyValueSeparator) { + public String join(final String separator, final String keyValueSeparator) { return MapKit.join(this.map, separator, keyValueSeparator); } @@ -199,7 +189,7 @@ public String join(String separator, final String keyValueSeparator) { * @param keyValueSeparator kv之间的连接符 * @return 连接后的字符串 */ - public String joinIgnoreNull(String separator, final String keyValueSeparator) { + public String joinIgnoreNull(final String separator, final String keyValueSeparator) { return MapKit.joinIgnoreNull(this.map, separator, keyValueSeparator); } @@ -211,7 +201,7 @@ public String joinIgnoreNull(String separator, final String keyValueSeparator) { * @param isIgnoreNull 是否忽略null的键和值 * @return 连接后的字符串 */ - public String join(String separator, final String keyValueSeparator, boolean isIgnoreNull) { + public String join(final String separator, final String keyValueSeparator, final boolean isIgnoreNull) { return MapKit.join(this.map, separator, keyValueSeparator, isIgnoreNull); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/MapJoiner.java b/bus-core/src/main/java/org/aoju/bus/core/map/MapJoiner.java new file mode 100755 index 0000000000..d6de1c87df --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/map/MapJoiner.java @@ -0,0 +1,135 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.map; + +import org.aoju.bus.core.text.TextJoiner; +import org.aoju.bus.core.toolkit.ArrayKit; +import org.aoju.bus.core.toolkit.StringKit; + +import java.util.Iterator; +import java.util.Map; +import java.util.function.Predicate; + +/** + * Map拼接器,可以拼接包括Map、Entry列表等 + * + * @author Kimi Liu + * @since Java 17+ + */ +public class MapJoiner { + + private final TextJoiner joiner; + private final String keyValueSeparator; + + /** + * 构造 + * + * @param joiner entry之间的Joiner + * @param keyValueSeparator kv之间的连接符 + */ + public MapJoiner(final TextJoiner joiner, final String keyValueSeparator) { + this.joiner = joiner; + this.keyValueSeparator = keyValueSeparator; + } + + /** + * 构建一个MapJoiner + * + * @param separator entry之间的连接符 + * @param keyValueSeparator kv之间的连接符 + * @return this + */ + public static MapJoiner of(final String separator, final String keyValueSeparator) { + return of(TextJoiner.of(separator), keyValueSeparator); + } + + /** + * 构建一个MapJoiner + * + * @param joiner entry之间的Joiner + * @param keyValueSeparator kv之间的连接符 + * @return this + */ + public static MapJoiner of(final TextJoiner joiner, final String keyValueSeparator) { + return new MapJoiner(joiner, keyValueSeparator); + } + + /** + * 追加Map + * + * @param 键类型 + * @param 值类型 + * @param map Map + * @param predicate Map过滤器 + * @return this + */ + public MapJoiner append(final Map map, final Predicate> predicate) { + return append(map.entrySet().iterator(), predicate); + } + + /** + * 追加Entry列表 + * + * @param 键类型 + * @param 值类型 + * @param parts Entry列表 + * @param predicate Map过滤器 + * @return this + */ + public MapJoiner append(final Iterator> parts, final Predicate> predicate) { + if (null == parts) { + return this; + } + + Map.Entry entry; + while (parts.hasNext()) { + entry = parts.next(); + if (null == predicate || predicate.test(entry)) { + joiner.append(TextJoiner.of(this.keyValueSeparator).append(entry.getKey()).append(entry.getValue())); + } + } + return this; + } + + /** + * 追加其他字符串,其他字符串简单拼接 + * + * @param params 字符串列表 + * @return this + */ + public MapJoiner append(final String... params) { + if (ArrayKit.isNotEmpty(params)) { + joiner.append(StringKit.concat(false, params)); + } + return this; + } + + @Override + public String toString() { + return joiner.toString(); + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/MapProxy.java b/bus-core/src/main/java/org/aoju/bus/core/map/MapProxy.java old mode 100755 new mode 100644 index 87d91dfae2..e610c30872 --- a/bus-core/src/main/java/org/aoju/bus/core/map/MapProxy.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/MapProxy.java @@ -58,7 +58,7 @@ public class MapProxy implements Map, TypeGetter, Invoca * * @param map 被代理的Map */ - public MapProxy(Map map) { + public MapProxy(final Map map) { this.map = map; } @@ -69,11 +69,12 @@ public MapProxy(Map map) { * @param map 被代理的Map * @return {@link MapProxy} */ - public static MapProxy create(Map map) { + public static MapProxy of(final Map map) { return (map instanceof MapProxy) ? (MapProxy) map : new MapProxy(map); } - public Object getObject(Object key, Object defaultValue) { + @Override + public Object getObject(final Object key, final Object defaultValue) { return map.getOrDefault(key, defaultValue); } @@ -88,32 +89,32 @@ public boolean isEmpty() { } @Override - public boolean containsKey(Object key) { + public boolean containsKey(final Object key) { return map.containsKey(key); } @Override - public boolean containsValue(Object value) { + public boolean containsValue(final Object value) { return map.containsValue(value); } @Override - public Object get(Object key) { + public Object get(final Object key) { return map.get(key); } @Override - public Object put(Object key, Object value) { + public Object put(final Object key, final Object value) { return map.put(key, value); } @Override - public Object remove(Object key) { + public Object remove(final Object key) { return map.remove(key); } @Override - public void putAll(Map m) { + public void putAll(final Map m) { map.putAll(m); } @@ -138,18 +139,18 @@ public Set> entrySet() { } @Override - public Object invoke(Object proxy, Method method, Object[] args) { + public Object invoke(final Object proxy, final Method method, final Object[] args) { final Class[] parameterTypes = method.getParameterTypes(); if (ArrayKit.isEmpty(parameterTypes)) { final Class returnType = method.getReturnType(); - if (null != returnType && void.class != returnType) { + if (void.class != returnType) { // 匹配Getter final String methodName = method.getName(); String fieldName = null; if (methodName.startsWith(Normal.GET)) { // 匹配getXXX fieldName = StringKit.removePreAndLowerFirst(methodName, 3); - } else if (BooleanKit.isBoolean(returnType) && methodName.startsWith(Normal.IS)) { + } else if (BooleanKit.isBoolean(returnType) && methodName.startsWith("is")) { // 匹配isXXX fieldName = StringKit.removePreAndLowerFirst(methodName, 2); } else if (Normal.HASHCODE.equals(methodName)) { @@ -194,7 +195,7 @@ public Object invoke(Object proxy, Method method, Object[] args) { * @param interfaceClass 接口 * @return 代理对象 */ - public T toProxyBean(Class interfaceClass) { + public T toProxyBean(final Class interfaceClass) { return (T) Proxy.newProxyInstance(ClassKit.getClassLoader(), new Class[]{interfaceClass}, this); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/MapWrapper.java b/bus-core/src/main/java/org/aoju/bus/core/map/MapWrapper.java index 59e296f592..195f1b71c1 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/map/MapWrapper.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/MapWrapper.java @@ -48,8 +48,6 @@ */ public class MapWrapper implements Map, Iterable>, XWrapper>, Serializable, Cloneable { - private static final long serialVersionUID = 1L; - /** * 默认增长因子 */ @@ -58,7 +56,7 @@ public class MapWrapper implements Map, Iterable>, X * 默认初始大小 */ protected static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; - + private static final long serialVersionUID = 1L; private Map raw; /** @@ -85,6 +83,7 @@ public MapWrapper(Supplier> mapFactory) { * * @return Map */ + @Override public Map getRaw() { return this.raw; } @@ -100,32 +99,32 @@ public boolean isEmpty() { } @Override - public boolean containsKey(Object key) { + public boolean containsKey(final Object key) { return raw.containsKey(key); } @Override - public boolean containsValue(Object value) { + public boolean containsValue(final Object value) { return raw.containsValue(value); } @Override - public V get(Object key) { + public V get(final Object key) { return raw.get(key); } @Override - public V put(K key, V value) { + public V put(final K key, final V value) { return raw.put(key, value); } @Override - public V remove(Object key) { + public V remove(final Object key) { return raw.remove(key); } @Override - public void putAll(Map m) { + public void putAll(final Map m) { raw.putAll(m); } @@ -155,14 +154,14 @@ public Iterator> iterator() { } @Override - public boolean equals(Object o) { + public boolean equals(final Object o) { if (this == o) { return true; } - if (null == o || getClass() != o.getClass()) { + if (o == null || getClass() != o.getClass()) { return false; } - MapWrapper that = (MapWrapper) o; + final MapWrapper that = (MapWrapper) o; return Objects.equals(raw, that.raw); } @@ -178,57 +177,57 @@ public String toString() { @Override - public void forEach(BiConsumer action) { + public void forEach(final BiConsumer action) { raw.forEach(action); } @Override - public void replaceAll(BiFunction function) { + public void replaceAll(final BiFunction function) { raw.replaceAll(function); } @Override - public V putIfAbsent(K key, V value) { + public V putIfAbsent(final K key, final V value) { return raw.putIfAbsent(key, value); } @Override - public boolean remove(Object key, Object value) { + public boolean remove(final Object key, final Object value) { return raw.remove(key, value); } @Override - public boolean replace(K key, V oldValue, V newValue) { + public boolean replace(final K key, final V oldValue, final V newValue) { return raw.replace(key, oldValue, newValue); } @Override - public V replace(K key, V value) { + public V replace(final K key, final V value) { return raw.replace(key, value); } @Override - public V computeIfAbsent(K key, Function mappingFunction) { + public V computeIfAbsent(final K key, final Function mappingFunction) { return raw.computeIfAbsent(key, mappingFunction); } @Override - public V getOrDefault(Object key, V defaultValue) { + public V getOrDefault(final Object key, final V defaultValue) { return raw.getOrDefault(key, defaultValue); } @Override - public V computeIfPresent(K key, BiFunction remappingFunction) { + public V computeIfPresent(final K key, final BiFunction remappingFunction) { return raw.computeIfPresent(key, remappingFunction); } @Override - public V compute(K key, BiFunction remappingFunction) { + public V compute(final K key, final BiFunction remappingFunction) { return raw.compute(key, remappingFunction); } @Override - public V merge(K key, V value, BiFunction remappingFunction) { + public V merge(final K key, final V value, final BiFunction remappingFunction) { return raw.merge(key, value, remappingFunction); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/MultiValueMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/MultiValueMap.java new file mode 100644 index 0000000000..15f197d27d --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/map/MultiValueMap.java @@ -0,0 +1,277 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.map; + +import org.aoju.bus.core.toolkit.ArrayKit; +import org.aoju.bus.core.toolkit.CollKit; + +import java.util.*; +import java.util.function.*; +import java.util.stream.Collectors; + +/** + * 一个键对应多个值的集合{@link Map}实现,提供针对键对应的值集合中的元素而非值集合本身的一些快捷操作, + * 本身可作为一个值为{@link Collection}类型的{@link Map}使用 + * + *

值集合类型

+ *

值集合的类型由接口的实现类自行维护,当通过{@link MultiValueMap}定义的方法进行增删改操作时, + * 实现类应保证通过通过实例方法获得的集合类型都一致但是若用户直接通过{@link Map}定义的方法进行增删改操作时, + * 实例无法保证通过实例方法获得的集合类型都一致 + * 因此,若无必要则更推荐通过{@link MultiValueMap}定义的方法进行操作 + * + *

对值集合的修改

+ * 当通过实例方法获得值集合时,若该集合允许修改,则对值集合的修改将会影响到其所属的{@link MultiValueMap}实例,反之亦然 + * 因此当同时遍历当前实例或者值集合时,若存在写操作,则需要注意可能引发的{@link ConcurrentModificationException} + * + * @author Kimi Liu + * @since Java 17+ + */ +public interface MultiValueMap extends Map> { + + /** + * 更新键对应的值集合 + * 注意:该操作将移除键对应的旧值集合,若仅需向值集合追加应值,则应使用{@link #putAllValues(Object, Collection)} + * + * @param key 键 + * @param value 键对应的新值集合 + * @return 旧值集合 + */ + @Override + Collection put(K key, Collection value); + + /** + * 更新全部键的值集合 + * 注意:该操作将移除键对应的旧值集合,若仅需向值集合追加应值,则应使用{@link #putAllValues(Object, Collection)} + * + * @param map 需要更新的键值对集合 + */ + @Override + void putAll(Map> map); + + /** + * 将集合中的全部键值对追加到当前实例中,效果等同于: + *
{@code
+     * for (Entry> entry : m.entrySet()) {
+     * 	K key = entry.getKey();
+     * 	Collection coll = entry.getValues();
+     * 	for (V val : coll) {
+     * 		map.putValue(key, val)
+     *    }
+     * }
+     * }
+ * + * @param m 待添加的集合 + */ + default void putAllValues(final Map> m) { + if (CollKit.isNotEmpty(m)) { + m.forEach(this::putAllValues); + } + } + + /** + * 将集合中的全部元素对追加到指定键对应的值集合中,效果等同于: + *
{@code
+     * 	for (V val : coll) {
+     * 		map.putValue(key, val)
+     *    }
+     * }
+ * + * @param key 键 + * @param coll 待添加的值集合 + * @return 是否成功添加 + */ + boolean putAllValues(K key, final Collection coll); + + /** + * 将数组中的全部元素追加到指定的值集合中,效果等同于: + *
{@code
+     * 	for (V val : values) {
+     * 		map.putValue(key, val)
+     *    }
+     * }
+ * + * @param key 键 + * @param values 待添加的值 + * @return boolean + */ + default boolean putValues(final K key, final V... values) { + return ArrayKit.isNotEmpty(values) && putAllValues(key, Arrays.asList(values)); + } + + /** + * 向指定键对应的值集合追加值,效果等同于: + *
{@code
+     * Collection coll = map.get(key);
+     * if(null == coll) {
+     * 	coll.add(value);
+     * 	map.put(coll);
+     * } else {
+     * 	coll.add(value);
+     * }
+     * }
+ * + * @param key 键 + * @param value 值 + * @return 是否成功添加 + */ + boolean putValue(final K key, final V value); + + /** + * 将值从指定键下的值集合中删除 + * + * @param key 键 + * @param value 值 + * @return 是否成功删除 + */ + boolean removeValue(final K key, final V value); + + /** + * 将一批值从指定键下的值集合中删除 + * + * @param key 键 + * @param values 值数组 + * @return 是否成功删除 + */ + default boolean removeValues(final K key, final V... values) { + return ArrayKit.isNotEmpty(values) && removeAllValues(key, Arrays.asList(values)); + } + + /** + * 将一批值从指定键下的值集合中删除 + * + * @param key 键 + * @param values 值集合 + * @return 是否成功删除 + */ + boolean removeAllValues(final K key, final Collection values); + + /** + * 根据条件过滤所有值集合中的值,并以新值生成新的值集合,新集合中的值集合类型与当前实例的默认值集合类型保持一致 + * + * @param filter 判断方法 + * @return 当前实例 + */ + default MultiValueMap filterAllValues(Predicate filter) { + return filterAllValues((k, v) -> filter.test(v)); + } + + /** + * 根据条件过滤所有值集合中的值,并以新值生成新的值集合,新集合中的值集合类型与当前实例的默认值集合类型保持一致 + * + * @param filter 判断方法 + * @return 当前实例 + */ + MultiValueMap filterAllValues(BiPredicate filter); + + /** + * 根据条件替换所有值集合中的值,并以新值生成新的值集合,新集合中的值集合类型与当前实例的默认值集合类型保持一致 + * + * @param operate 替换方法 + * @return 当前实例 + */ + default MultiValueMap replaceAllValues(UnaryOperator operate) { + return replaceAllValues((k, v) -> operate.apply(v)); + } + + /** + * 根据条件替换所有值集合中的值,并以新值生成新的值集合,新集合中的值集合类型与当前实例的默认值集合类型保持一致 + * + * @param operate 替换方法 + * @return 当前实例 + */ + MultiValueMap replaceAllValues(BiFunction operate); + + /** + * 获取指定序号的值,若值不存在,返回{@code null} + * + * @param key 键 + * @param index 第几个值的索引,越界返回null + * @return 值或null + */ + default V getValue(K key, int index) { + final Collection collection = get(key); + return CollKit.get(collection, index); + } + + /** + * 获取键对应的值,若值不存在,则返回{@link Collections#emptyList()}效果等同于: + *
{@code
+     * map.getOrDefault(key, Collections.emptyList())
+     * }
+ * + * @param key 键 + * @return 值集合 + */ + default Collection getValues(final K key) { + return getOrDefault(key, Collections.emptyList()); + } + + /** + * 获取键对应值的数量,若键对应的值不存在,则返回{@code 0} + * + * @param key 键 + * @return 值的数量 + */ + default int size(final K key) { + return getValues(key).size(); + } + + /** + * 遍历所有键值对,效果等同于: + *
{@code
+     * for (Entry> entry : entrySet()) {
+     * 	K key = entry.getKey();
+     * 	Collection coll = entry.getValues();
+     * 	for (V val : coll) {
+     * 		consumer.accept(key, val);
+     *    }
+     * }
+     * }
+ * + * @param consumer 操作 + */ + default void allForEach(BiConsumer consumer) { + forEach((k, coll) -> coll.forEach(v -> consumer.accept(k, v))); + } + + /** + * 获取所有的值,效果等同于: + *
{@code
+     * List results = new ArrayList<>();
+     * for (Collection coll : values()) {
+     * 	results.addAll(coll);
+     * }
+     * }
+ * + * @return 值 + */ + default Collection allValues() { + return values().stream() + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/ReferenceMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/ReferenceMap.java index f54000471b..8b78a078ad 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/map/ReferenceMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/ReferenceMap.java @@ -66,7 +66,7 @@ public class ReferenceMap implements ConcurrentMap, Iterable, V> raw, References.Type referenceType) { + public ReferenceMap(final ConcurrentMap, V> raw, final References.Type referenceType) { this.raw = raw; this.keyType = referenceType; lastQueue = new ReferenceQueue<>(); @@ -157,6 +157,7 @@ public V computeIfPresent(final K key, final BiFunction remappingFunction.apply(key, value)); } + @SuppressWarnings("unchecked") @Override public V remove(final Object key) { this.purgeStaleKeys(); diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/RowKeyTable.java b/bus-core/src/main/java/org/aoju/bus/core/map/RowKeyTable.java old mode 100644 new mode 100755 index 9c23623b59..964aece7c0 --- a/bus-core/src/main/java/org/aoju/bus/core/map/RowKeyTable.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/RowKeyTable.java @@ -65,7 +65,7 @@ public RowKeyTable() { * * @param isLinked 是否有序,有序则使用{@link java.util.LinkedHashMap}作为原始Map */ - public RowKeyTable(boolean isLinked) { + public RowKeyTable(final boolean isLinked) { this(MapKit.newHashMap(isLinked), () -> MapKit.newHashMap(isLinked)); } @@ -74,7 +74,7 @@ public RowKeyTable(boolean isLinked) { * * @param raw 原始Map */ - public RowKeyTable(Map> raw) { + public RowKeyTable(final Map> raw) { this(raw, HashMap::new); } @@ -84,7 +84,7 @@ public RowKeyTable(Map> raw) { * @param raw 原始Map * @param columnMapBuilder 列的map创建器 */ - public RowKeyTable(Map> raw, Builder> columnMapBuilder) { + public RowKeyTable(final Map> raw, final Builder> columnMapBuilder) { this.raw = raw; this.columnBuilder = null == columnMapBuilder ? HashMap::new : columnMapBuilder; } @@ -95,12 +95,12 @@ public Map> rowMap() { } @Override - public V put(R rowKey, C columnKey, V value) { + public V put(final R rowKey, final C columnKey, final V value) { return raw.computeIfAbsent(rowKey, (key) -> columnBuilder.build()).put(columnKey, value); } @Override - public V remove(R rowKey, C columnKey) { + public V remove(final R rowKey, final C columnKey) { final Map map = getRow(rowKey); if (null == map) { return null; @@ -123,11 +123,11 @@ public void clear() { } @Override - public boolean containsColumn(C columnKey) { + public boolean containsColumn(final C columnKey) { if (columnKey == null) { return false; } - for (Map map : raw.values()) { + for (final Map map : raw.values()) { if (null != map && map.containsKey(columnKey)) { return true; } @@ -135,16 +135,15 @@ public boolean containsColumn(C columnKey) { return false; } - //region columnMap @Override public Map> columnMap() { - Map> result = columnMap; + final Map> result = columnMap; return (result == null) ? columnMap = new ColumnMap() : result; } @Override public Set columnKeySet() { - Set result = columnKeySet; + final Set result = columnKeySet; return (result == null) ? columnKeySet = new ColumnKeySet() : result; } @@ -152,7 +151,7 @@ public Set columnKeySet() { public List columnKeys() { final Collection> values = this.raw.values(); final List result = new ArrayList<>(values.size() * 16); - for (Map map : values) { + for (final Map map : values) { map.forEach((key, value) -> { result.add(key); }); @@ -161,7 +160,7 @@ public List columnKeys() { } @Override - public Map getColumn(C columnKey) { + public Map getColumn(final C columnKey) { return new Column(columnKey); } @@ -209,7 +208,7 @@ private class ColumnKeyIterator extends ComputeIterator { protected C computeNext() { while (true) { if (entryIterator.hasNext()) { - Map.Entry entry = entryIterator.next(); + final Map.Entry entry = entryIterator.next(); if (false == seen.containsKey(entry.getKey())) { seen.put(entry.getKey(), entry.getValue()); return entry.getKey(); @@ -226,7 +225,7 @@ protected C computeNext() { private class Column extends AbstractMap { final C columnKey; - Column(C columnKey) { + Column(final C columnKey) { this.columnKey = columnKey; } @@ -235,17 +234,17 @@ public Set> entrySet() { return new EntrySet(); } - private class EntrySet extends AbstractSet> { + private class EntrySet extends AbstractSet> { @Override - public Iterator> iterator() { + public Iterator> iterator() { return new EntrySetIterator(); } @Override public int size() { int size = 0; - for (Map map : raw.values()) { + for (final Map map : raw.values()) { if (map.containsKey(columnKey)) { size++; } @@ -262,7 +261,7 @@ protected Entry computeNext() { while (iterator.hasNext()) { final Entry> entry = iterator.next(); if (entry.getValue().containsKey(columnKey)) { - return new AbstractEntry() { + return new AbstractEntry<>() { @Override public R getKey() { return entry.getKey(); @@ -274,7 +273,7 @@ public V getValue() { } @Override - public V setValue(V value) { + public V setValue(final V value) { return entry.getValue().put(columnKey, value); } }; diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/LongMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/SafeHashMap.java similarity index 59% rename from bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/LongMap.java rename to bus-core/src/main/java/org/aoju/bus/core/map/SafeHashMap.java index 385a99a9b4..0540d95723 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/LongMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/SafeHashMap.java @@ -23,57 +23,78 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.core.bloom.bitmap; +package org.aoju.bus.core.map; -import java.io.Serializable; +import org.aoju.bus.core.toolkit.MapKit; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; /** - * 过滤器BitMap在64位机器上.这个类能发生更好的效果.一般机器不建议使用 + * 安全的ConcurrentHashMap实现 + * 此类用于解决在JDK8中调用{@link ConcurrentHashMap#computeIfAbsent(Object, Function)}可能造成的死循环问题 * + * @param 键类型 + * @param 值类型 * @author Kimi Liu * @since Java 17+ */ -public class LongMap implements BitMap, Serializable { +public class SafeHashMap extends ConcurrentHashMap { private static final long serialVersionUID = 1L; - private final long[] longs; + /** + * 构造,默认初始大小(16) + */ + public SafeHashMap() { + super(); + } /** * 构造 + * + * @param initialCapacity 预估初始大小 */ - public LongMap() { - longs = new long[93750000]; + public SafeHashMap(int initialCapacity) { + super(initialCapacity); } /** * 构造 * - * @param size 容量 + * @param map 初始键值对 */ - public LongMap(int size) { - longs = new long[size]; + public SafeHashMap(Map map) { + super(map); } - @Override - public void add(long i) { - int r = (int) (i / BitMap.MACHINE64); - long c = i % BitMap.MACHINE64; - longs[r] = longs[r] | (1L << c); + /** + * 构造 + * + * @param initialCapacity 初始容量 + * @param loadFactor 增长系数 + */ + public SafeHashMap(int initialCapacity, float loadFactor) { + super(initialCapacity, loadFactor); } - @Override - public boolean contains(long i) { - int r = (int) (i / BitMap.MACHINE64); - long c = i % BitMap.MACHINE64; - return ((longs[r] >>> c) & 1) == 1; + /** + * 构造 + * + * @param initialCapacity 初始容量 + * @param loadFactor 增长系数 + * @param concurrencyLevel 并发级别,即Segment的个数 + */ + public SafeHashMap(int initialCapacity, + float loadFactor, + int concurrencyLevel) { + super(initialCapacity, loadFactor, concurrencyLevel); } @Override - public void remove(long i) { - int r = (int) (i / BitMap.MACHINE64); - long c = i % BitMap.MACHINE64; - longs[r] &= ~(1L << c); + public V computeIfAbsent(K key, Function mappingFunction) { + return MapKit.computeIfAbsent(this, key, mappingFunction); } -} \ No newline at end of file +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/SetValueMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/SetValueMap.java old mode 100755 new mode 100644 index fc5062f4f5..5e225a8808 --- a/bus-core/src/main/java/org/aoju/bus/core/map/SetValueMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/SetValueMap.java @@ -26,6 +26,7 @@ package org.aoju.bus.core.map; import java.util.*; +import java.util.function.Supplier; /** * 值作为集合Set(LinkedHashSet)的Map实现,通过调用putValue可以在相同key时加入多个值,多个值用集合表示 @@ -35,56 +36,37 @@ * @author Kimi Liu * @since Java 17+ */ -public class SetValueMap extends AbstractCollValueMap> { +public class SetValueMap extends AbstractCollValueMap { - /** - * 构造 - */ - public SetValueMap() { - this(DEFAULT_INITIAL_CAPACITY); - } + private static final long serialVersionUID = 1L; - /** - * 构造 - * - * @param initialCapacity 初始大小 - */ - public SetValueMap(int initialCapacity) { - this(initialCapacity, DEFAULT_LOAD_FACTOR); - } /** - * 构造 - * - * @param m Map + * 基于{@link HashMap}创建一个值为{@link Set}的多值映射集合 */ - public SetValueMap(Map> m) { - this(DEFAULT_LOAD_FACTOR, m); + public SetValueMap() { } /** - * 构造 + * 基于{@link HashMap}创建一个值为{@link Set}的多值映射集合 * - * @param loadFactor 加载因子 - * @param m Map + * @param map 提供数据的原始集合 */ - public SetValueMap(float loadFactor, Map> m) { - this(m.size(), loadFactor); - this.putAllValues(m); + public SetValueMap(Map> map) { + super(map); } /** - * 构造 + * 基于{@code mapFactory}创建一个值为{@link Set}的多值映射集合 * - * @param initialCapacity 初始大小 - * @param loadFactor 加载因子 + * @param mapFactory 创建集合的工厂反方 */ - public SetValueMap(int initialCapacity, float loadFactor) { - super(new HashMap<>(initialCapacity, loadFactor)); + public SetValueMap(Supplier>> mapFactory) { + super(mapFactory); } @Override - protected Set createCollection() { + public Set createCollection() { return new LinkedHashSet<>(DEFAULT_COLLECTION_INITIAL_CAPACITY); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/Table.java b/bus-core/src/main/java/org/aoju/bus/core/map/Table.java old mode 100644 new mode 100755 index 64410a68bc..8c29ff567d --- a/bus-core/src/main/java/org/aoju/bus/core/map/Table.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/Table.java @@ -51,7 +51,7 @@ public interface Table extends Iterable> { * @param columnKey 列键 * @return 是否包含映射 */ - default boolean contains(R rowKey, C columnKey) { + default boolean contains(final R rowKey, final C columnKey) { return Optional.ofNullable(getRow(rowKey)).map((map) -> map.containsKey(columnKey)).get(); } @@ -61,7 +61,7 @@ default boolean contains(R rowKey, C columnKey) { * @param rowKey 行键 * @return 行是否存在 */ - default boolean containsRow(R rowKey) { + default boolean containsRow(final R rowKey) { return Optional.ofNullable(rowMap()).map((map) -> map.containsKey(rowKey)).get(); } @@ -71,7 +71,7 @@ default boolean containsRow(R rowKey) { * @param rowKey 行键 * @return 行映射,返回的键为列键,值为表格的值 */ - default Map getRow(R rowKey) { + default Map getRow(final R rowKey) { return Optional.ofNullable(rowMap()).map((map) -> map.get(rowKey)).get(); } @@ -97,7 +97,7 @@ default Set rowKeySet() { * @param columnKey 列键 * @return 列是否存在 */ - default boolean containsColumn(C columnKey) { + default boolean containsColumn(final C columnKey) { return Optional.ofNullable(columnMap()).map((map) -> map.containsKey(columnKey)).get(); } @@ -107,7 +107,7 @@ default boolean containsColumn(C columnKey) { * @param columnKey 列键 * @return 列映射,返回的键为行键,值为表格的值 */ - default Map getColumn(C columnKey) { + default Map getColumn(final C columnKey) { return Optional.ofNullable(columnMap()).map((map) -> map.get(columnKey)).get(); } @@ -132,7 +132,7 @@ default List columnKeys() { } final List result = new ArrayList<>(columnMap.size()); - for (Map.Entry> cMapEntry : columnMap.entrySet()) { + for (final Map.Entry> cMapEntry : columnMap.entrySet()) { result.add(cMapEntry.getKey()); } return result; @@ -151,10 +151,10 @@ default List columnKeys() { * @param value 值 * @return 值 */ - default boolean containsValue(V value) { + default boolean containsValue(final V value) { final Collection> rows = Optional.ofNullable(rowMap()).map(Map::values).get(); if (null != rows) { - for (Map row : rows) { + for (final Map row : rows) { if (row.containsValue(value)) { return true; } @@ -170,7 +170,7 @@ default boolean containsValue(V value) { * @param columnKey 列键 * @return 值,如果值不存在,返回{@code null} */ - default V get(R rowKey, C columnKey) { + default V get(final R rowKey, final C columnKey) { return Optional.ofNullable(getRow(rowKey)).map((map) -> map.get(columnKey)).get(); } @@ -203,9 +203,9 @@ default V get(R rowKey, C columnKey) { * * @param table 其他table */ - default void putAll(Table table) { + default void putAll(final Table table) { if (null != table) { - for (Cell cell : table.cellSet()) { + for (final Cell cell : table.cellSet()) { put(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); } } @@ -238,7 +238,7 @@ default int size() { return 0; } int size = 0; - for (Map map : rowMap.values()) { + for (final Map map : rowMap.values()) { size += map.size(); } return size; @@ -254,8 +254,8 @@ default int size() { * * @param consumer 单元格值处理器 */ - default void forEach(XMultiple consumer) { - for (Cell cell : this) { + default void forEach(final XMultiple consumer) { + for (final Cell cell : this) { consumer.accept(cell.getRowKey(), cell.getColumnKey(), cell.getValue()); } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/TableMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/TableMap.java index ebfa5d275e..30d8b497a3 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/map/TableMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/TableMap.java @@ -31,6 +31,8 @@ import java.io.Serializable; import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; /** * 无重复键的Map @@ -42,6 +44,8 @@ */ public class TableMap implements Map, Iterable>, Serializable { + private static final long serialVersionUID = 1L; + private final List keys; private final List values; @@ -57,7 +61,7 @@ public TableMap() { * * @param size 初始容量 */ - public TableMap(int size) { + public TableMap(final int size) { this.keys = new ArrayList<>(size); this.values = new ArrayList<>(size); } @@ -68,7 +72,7 @@ public TableMap(int size) { * @param keys 键列表 * @param values 值列表 */ - public TableMap(K[] keys, V[] values) { + public TableMap(final K[] keys, final V[] values) { this.keys = CollKit.toList(keys); this.values = CollKit.toList(values); } @@ -84,19 +88,19 @@ public boolean isEmpty() { } @Override - public boolean containsKey(Object key) { + public boolean containsKey(final Object key) { return keys.contains(key); } @Override - public boolean containsValue(Object value) { + public boolean containsValue(final Object value) { return values.contains(value); } @Override - public V get(Object key) { + public V get(final Object key) { final int index = keys.indexOf(key); - if (index > -1 && index < values.size()) { + if (index > -1) { return values.get(index); } return null; @@ -108,9 +112,9 @@ public V get(Object key) { * @param value 值 * @return 键 */ - public K getKey(V value) { + public K getKey(final V value) { final int index = values.indexOf(value); - if (index > -1 && index < keys.size()) { + if (index > -1) { return keys.get(index); } return null; @@ -122,7 +126,7 @@ public K getKey(V value) { * @param key 键 * @return 值列表 */ - public List getValues(K key) { + public List getValues(final K key) { return CollKit.getAny( this.values, CollKit.indexOfAll(this.keys, (ele) -> ObjectKit.equals(ele, key)) @@ -135,7 +139,7 @@ public List getValues(K key) { * @param value 值 * @return 值列表 */ - public List getKeys(V value) { + public List getKeys(final V value) { return CollKit.getAny( this.keys, CollKit.indexOfAll(this.values, (ele) -> ObjectKit.equals(ele, value)) @@ -143,27 +147,42 @@ public List getKeys(V value) { } @Override - public V put(K key, V value) { + public V put(final K key, final V value) { keys.add(key); values.add(value); return null; } + /** + * 移除指定的所有键和对应的所有值 + * + * @param key 键 + * @return 最后一个移除的值 + */ @Override - public V remove(Object key) { - int index = keys.indexOf(key); - if (index > -1) { - keys.remove(index); - if (index < values.size()) { - values.remove(index); - } + public V remove(final Object key) { + V lastValue = null; + int index; + while ((index = keys.indexOf(key)) > -1) { + lastValue = removeByIndex(index); } - return null; + return lastValue; + } + + /** + * 移除指定位置的键值对 + * + * @param index 位置,不能越界 + * @return 移除的值 + */ + public V removeByIndex(final int index) { + keys.remove(index); + return values.remove(index); } @Override - public void putAll(Map m) { - for (Map.Entry entry : m.entrySet()) { + public void putAll(final Map m) { + for (final Entry entry : m.entrySet()) { this.put(entry.getKey(), entry.getValue()); } } @@ -194,8 +213,8 @@ public Collection values() { } @Override - public Set> entrySet() { - final Set> hashSet = new LinkedHashSet<>(); + public Set> entrySet() { + final Set> hashSet = new LinkedHashSet<>(); for (int i = 0; i < size(); i++) { hashSet.add(MapKit.entry(keys.get(i), values.get(i))); } @@ -203,7 +222,7 @@ public Set> entrySet() { } @Override - public Iterator> iterator() { + public Iterator> iterator() { return new Iterator<>() { private final Iterator keysIter = keys.iterator(); private final Iterator valuesIter = values.iterator(); @@ -214,7 +233,7 @@ public boolean hasNext() { } @Override - public Map.Entry next() { + public Entry next() { return MapKit.entry(keysIter.next(), valuesIter.next()); } @@ -228,7 +247,90 @@ public void remove() { @Override public String toString() { - return "TableMap{" + "keys=" + keys + ", values=" + values + '}'; + return "TableMap{" + + "keys=" + keys + + ", values=" + values + + '}'; + } + + @Override + public void forEach(final BiConsumer action) { + for (int i = 0; i < size(); i++) { + action.accept(keys.get(i), values.get(i)); + } + } + + @Override + public boolean remove(final Object key, final Object value) { + boolean removed = false; + for (int i = 0; i < size(); i++) { + if (ObjectKit.equals(key, keys.get(i)) && ObjectKit.equals(value, values.get(i))) { + removeByIndex(i); + removed = true; + // 移除当前元素,下个元素前移 + i--; + } + } + return removed; + } + + @Override + public void replaceAll(final BiFunction function) { + for (int i = 0; i < size(); i++) { + final V newValue = function.apply(keys.get(i), values.get(i)); + values.set(i, newValue); + } + } + + @Override + public boolean replace(final K key, final V oldValue, final V newValue) { + for (int i = 0; i < size(); i++) { + if (ObjectKit.equals(key, keys.get(i)) && ObjectKit.equals(oldValue, values.get(i))) { + values.set(i, newValue); + return true; + } + } + return false; + } + + /** + * 替换指定key的所有值为指定值 + * + * @param key 指定的key + * @param value 替换的值 + * @return 最后替换的值 + */ + @Override + public V replace(final K key, final V value) { + V lastValue = null; + for (int i = 0; i < size(); i++) { + if (ObjectKit.equals(key, keys.get(i))) { + lastValue = values.set(i, value); + } + } + return lastValue; + } + + @Override + public V computeIfPresent(final K key, final BiFunction remappingFunction) { + if (null == remappingFunction) { + return null; + } + + V lastValue = null; + for (int i = 0; i < size(); i++) { + if (ObjectKit.equals(key, keys.get(i))) { + final V newValue = remappingFunction.apply(key, values.get(i)); + if (null != newValue) { + lastValue = values.set(i, newValue); + } else { + removeByIndex(i); + // 移除当前元素,下个元素前移 + i--; + } + } + } + return lastValue; } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/TolerantMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/TolerantMap.java index c9c170bde7..3834a9e675 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/map/TolerantMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/TolerantMap.java @@ -46,7 +46,7 @@ public class TolerantMap extends MapWrapper { * * @param defaultValue 默认值 */ - public TolerantMap(V defaultValue) { + public TolerantMap(final V defaultValue) { this(new HashMap<>(), defaultValue); } @@ -57,7 +57,7 @@ public TolerantMap(V defaultValue) { * @param loadFactor 增长因子 * @param defaultValue 默认值 */ - public TolerantMap(int initialCapacity, float loadFactor, V defaultValue) { + public TolerantMap(final int initialCapacity, final float loadFactor, final V defaultValue) { this(new HashMap<>(initialCapacity, loadFactor), defaultValue); } @@ -67,7 +67,7 @@ public TolerantMap(int initialCapacity, float loadFactor, V defaultValue) { * @param initialCapacity 初始容量 * @param defaultValue 默认值 */ - public TolerantMap(int initialCapacity, V defaultValue) { + public TolerantMap(final int initialCapacity, final V defaultValue) { this(new HashMap<>(initialCapacity), defaultValue); } @@ -77,7 +77,7 @@ public TolerantMap(int initialCapacity, V defaultValue) { * @param map Map实现 * @param defaultValue 默认值 */ - public TolerantMap(Map map, V defaultValue) { + public TolerantMap(final Map map, final V defaultValue) { super(map); this.defaultValue = defaultValue; } @@ -89,30 +89,31 @@ public TolerantMap(Map map, V defaultValue) { * @param defaultValue 默认值 * @param 键类型 * @param 值类型 - * @return TolerantMap + * @return this */ - public static TolerantMap of(Map map, V defaultValue) { + public static TolerantMap of(final Map map, final V defaultValue) { return new TolerantMap<>(map, defaultValue); } @Override - public V get(Object key) { + public V get(final Object key) { return getOrDefault(key, defaultValue); } @Override - public boolean equals(Object o) { + public boolean equals(final Object o) { if (this == o) { return true; } - if (null == o || getClass() != o.getClass()) { + if (o == null || getClass() != o.getClass()) { return false; } if (false == super.equals(o)) { return false; } final TolerantMap that = (TolerantMap) o; - return getRaw().equals(that.getRaw()) && Objects.equals(defaultValue, that.defaultValue); + return getRaw().equals(that.getRaw()) + && Objects.equals(defaultValue, that.defaultValue); } @Override diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/TransitionMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/TransitionMap.java old mode 100644 new mode 100755 index 3a23356a72..86f4ac510e --- a/bus-core/src/main/java/org/aoju/bus/core/map/TransitionMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/TransitionMap.java @@ -49,7 +49,7 @@ public abstract class TransitionMap extends MapWrapper { * * @param mapFactory 空Map创建工厂 */ - public TransitionMap(Supplier> mapFactory) { + public TransitionMap(final Supplier> mapFactory) { super(mapFactory); } @@ -59,67 +59,67 @@ public TransitionMap(Supplier> mapFactory) { * * @param emptyMap Map 被包装的Map,必须为空Map,否则自定义key会无效 */ - public TransitionMap(Map emptyMap) { + public TransitionMap(final Map emptyMap) { super(emptyMap); } @Override - public V get(Object key) { + public V get(final Object key) { return super.get(customKey(key)); } @Override - public V put(K key, V value) { + public V put(final K key, final V value) { return super.put(customKey(key), customValue(value)); } @Override - public void putAll(Map m) { + public void putAll(final Map m) { m.forEach(this::put); } @Override - public boolean containsKey(Object key) { + public boolean containsKey(final Object key) { return super.containsKey(customKey(key)); } @Override - public V remove(Object key) { + public V remove(final Object key) { return super.remove(customKey(key)); } @Override - public boolean remove(Object key, Object value) { + public boolean remove(final Object key, final Object value) { return super.remove(customKey(key), customValue(value)); } @Override - public boolean replace(K key, V oldValue, V newValue) { + public boolean replace(final K key, final V oldValue, final V newValue) { return super.replace(customKey(key), customValue(oldValue), customValue(newValue)); } @Override - public V replace(K key, V value) { + public V replace(final K key, final V value) { return super.replace(customKey(key), customValue(value)); } @Override - public V getOrDefault(Object key, V defaultValue) { + public V getOrDefault(final Object key, final V defaultValue) { return super.getOrDefault(customKey(key), customValue(defaultValue)); } @Override - public V computeIfPresent(K key, BiFunction remappingFunction) { + public V computeIfPresent(final K key, final BiFunction remappingFunction) { return super.computeIfPresent(customKey(key), (k, v) -> remappingFunction.apply(customKey(k), customValue(v))); } @Override - public V compute(K key, BiFunction remappingFunction) { + public V compute(final K key, final BiFunction remappingFunction) { return super.compute(customKey(key), (k, v) -> remappingFunction.apply(customKey(k), customValue(v))); } @Override - public V merge(K key, V value, BiFunction remappingFunction) { + public V merge(final K key, final V value, final BiFunction remappingFunction) { return super.merge(customKey(key), customValue(value), (v1, v2) -> remappingFunction.apply(customValue(v1), customValue(v2))); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/TreeEntry.java b/bus-core/src/main/java/org/aoju/bus/core/map/TreeEntry.java index b1fac0a9c3..145630b489 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/map/TreeEntry.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/TreeEntry.java @@ -43,25 +43,6 @@ */ public interface TreeEntry extends Map.Entry { - /** - * 比较目标对象与当前{@link TreeEntry}是否相等。
- * 默认只要{@link TreeEntry#getKey()}的返回值相同,即认为两者相等 - * - * @param o 目标对象 - * @return 是否 - */ - @Override - boolean equals(Object o); - - /** - * 返回当前{@link TreeEntry}的哈希值。
- * 默认总是返回{@link TreeEntry#getKey()}的哈希值 - * - * @return 哈希值 - */ - @Override - int hashCode(); - /** * 获取以当前节点作为叶子节点的树结构,然后获取当前节点与根节点的距离 * diff --git a/bus-core/src/main/java/org/aoju/bus/core/map/WeakMap.java b/bus-core/src/main/java/org/aoju/bus/core/map/WeakMap.java index f62933825b..9159d78da0 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/map/WeakMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/map/WeakMap.java @@ -28,7 +28,6 @@ import org.aoju.bus.core.lang.References; import java.lang.ref.Reference; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** @@ -42,11 +41,13 @@ */ public class WeakMap extends ReferenceMap { + private static final long serialVersionUID = 1L; + /** * 构造 */ public WeakMap() { - this(new ConcurrentHashMap<>()); + this(new SafeHashMap<>()); } /** @@ -54,7 +55,7 @@ public WeakMap() { * * @param raw {@link ConcurrentMap}实现 */ - public WeakMap(ConcurrentMap, V> raw) { + public WeakMap(final ConcurrentMap, V> raw) { super(raw, References.Type.WEAK); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/IntMap.java b/bus-core/src/main/java/org/aoju/bus/core/net/LocalPort.java similarity index 69% rename from bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/IntMap.java rename to bus-core/src/main/java/org/aoju/bus/core/net/LocalPort.java index 132994e0d9..7eabeb78fd 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/bitmap/IntMap.java +++ b/bus-core/src/main/java/org/aoju/bus/core/net/LocalPort.java @@ -23,57 +23,51 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.core.bloom.bitmap; +package org.aoju.bus.core.net; + +import org.aoju.bus.core.toolkit.NetKit; import java.io.Serializable; +import java.util.concurrent.atomic.AtomicInteger; /** - * 过滤器BitMap在32位机器上.这个类能发生更好的效果.一般情况下建议使用此类 + * 本地端口生成器Percent + * 用于生成本地可用(未被占用)的端口号Percent + * 注意:多线程甚至单线程访问时可能会返回同一端口(例如获取了端口但是没有使用) * * @author Kimi Liu * @since Java 17+ */ -public class IntMap implements BitMap, Serializable { +public class LocalPort implements Serializable { private static final long serialVersionUID = 1L; - private final int[] ints; - /** - * 构造 + * 备选的本地端口 */ - public IntMap() { - ints = new int[93750000]; - } + private final AtomicInteger alternativePort; /** * 构造 * - * @param size 容量 + * @param beginPort 起始端口号 */ - public IntMap(int size) { - ints = new int[size]; - } - - @Override - public void add(long i) { - int r = (int) (i / BitMap.MACHINE32); - int c = (int) (i % BitMap.MACHINE32); - ints[r] = ints[r] | (1 << c); + public LocalPort(final int beginPort) { + alternativePort = new AtomicInteger(beginPort); } - @Override - public boolean contains(long i) { - int r = (int) (i / BitMap.MACHINE32); - int c = (int) (i % BitMap.MACHINE32); - return ((ints[r] >>> c) & 1) == 1; - } - - @Override - public void remove(long i) { - int r = (int) (i / BitMap.MACHINE32); - int c = (int) (i % BitMap.MACHINE32); - ints[r] &= ~(1 << c); + /** + * 生成一个本地端口,用于远程端口映射 + * + * @return 未被使用的本地端口 + */ + public int generate() { + int validPort = alternativePort.get(); + // 获取可用端口 + while (false == NetKit.isUsableLocalPort(validPort)) { + validPort = alternativePort.incrementAndGet(); + } + return validPort; } -} \ No newline at end of file +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/net/MaskBit.java b/bus-core/src/main/java/org/aoju/bus/core/net/MaskBit.java new file mode 100644 index 0000000000..b1e11e19b3 --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/net/MaskBit.java @@ -0,0 +1,101 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.net; + +import org.aoju.bus.core.map.DuplexingMap; + +import java.util.HashMap; + +/** + * 掩码位和掩码之间的Map对应 + * + * @author Kimi Liu + * @since Java 17+ + */ +public class MaskBit { + + /** + * 掩码位与掩码的点分十进制的双向对应关系 + */ + private static final DuplexingMap MASK_BIT_MAP; + + static { + MASK_BIT_MAP = new DuplexingMap<>(new HashMap<>(32)); + MASK_BIT_MAP.put(1, "128.0.0.0"); + MASK_BIT_MAP.put(2, "192.0.0.0"); + MASK_BIT_MAP.put(3, "224.0.0.0"); + MASK_BIT_MAP.put(4, "240.0.0.0"); + MASK_BIT_MAP.put(5, "248.0.0.0"); + MASK_BIT_MAP.put(6, "252.0.0.0"); + MASK_BIT_MAP.put(7, "254.0.0.0"); + MASK_BIT_MAP.put(8, "255.0.0.0"); + MASK_BIT_MAP.put(9, "255.128.0.0"); + MASK_BIT_MAP.put(10, "255.192.0.0"); + MASK_BIT_MAP.put(11, "255.224.0.0"); + MASK_BIT_MAP.put(12, "255.240.0.0"); + MASK_BIT_MAP.put(13, "255.248.0.0"); + MASK_BIT_MAP.put(14, "255.252.0.0"); + MASK_BIT_MAP.put(15, "255.254.0.0"); + MASK_BIT_MAP.put(16, "255.255.0.0"); + MASK_BIT_MAP.put(17, "255.255.128.0"); + MASK_BIT_MAP.put(18, "255.255.192.0"); + MASK_BIT_MAP.put(19, "255.255.224.0"); + MASK_BIT_MAP.put(20, "255.255.240.0"); + MASK_BIT_MAP.put(21, "255.255.248.0"); + MASK_BIT_MAP.put(22, "255.255.252.0"); + MASK_BIT_MAP.put(23, "255.255.254.0"); + MASK_BIT_MAP.put(24, "255.255.255.0"); + MASK_BIT_MAP.put(25, "255.255.255.128"); + MASK_BIT_MAP.put(26, "255.255.255.192"); + MASK_BIT_MAP.put(27, "255.255.255.224"); + MASK_BIT_MAP.put(28, "255.255.255.240"); + MASK_BIT_MAP.put(29, "255.255.255.248"); + MASK_BIT_MAP.put(30, "255.255.255.252"); + MASK_BIT_MAP.put(31, "255.255.255.254"); + MASK_BIT_MAP.put(32, "255.255.255.255"); + } + + /** + * 根据掩码位获取掩码 + * + * @param maskBit 掩码位 + * @return 掩码 + */ + public static String get(final int maskBit) { + return MASK_BIT_MAP.get(maskBit); + } + + /** + * 根据掩码获取掩码位 + * + * @param mask 掩码的点分十进制表示,如 255.255.255.0 + * @return 掩码位,如 24;如果掩码不合法,则返回null + */ + public static Integer getMaskBit(final String mask) { + return MASK_BIT_MAP.getKey(mask); + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/net/package-info.java b/bus-core/src/main/java/org/aoju/bus/core/net/package-info.java new file mode 100644 index 0000000000..4fde717922 --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/net/package-info.java @@ -0,0 +1,7 @@ +/** + * 网络相关工具 + * + * @author Kimi Liu + * @since Java 17+ + */ +package org.aoju.bus.core.net; \ No newline at end of file diff --git a/bus-http/src/main/java/org/aoju/bus/http/secure/DefaultTrustManager.java b/bus-core/src/main/java/org/aoju/bus/core/net/tls/DefaultTrustManager.java similarity index 98% rename from bus-http/src/main/java/org/aoju/bus/http/secure/DefaultTrustManager.java rename to bus-core/src/main/java/org/aoju/bus/core/net/tls/DefaultTrustManager.java index 354d15a23d..a3471ffe2a 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/secure/DefaultTrustManager.java +++ b/bus-core/src/main/java/org/aoju/bus/core/net/tls/DefaultTrustManager.java @@ -23,7 +23,7 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.http.secure; +package org.aoju.bus.core.net.tls; import javax.net.ssl.SSLEngine; import javax.net.ssl.X509ExtendedTrustManager; diff --git a/bus-http/src/main/java/org/aoju/bus/http/secure/HostnameVerifier.java b/bus-core/src/main/java/org/aoju/bus/core/net/tls/HostnameVerifier.java similarity index 98% rename from bus-http/src/main/java/org/aoju/bus/http/secure/HostnameVerifier.java rename to bus-core/src/main/java/org/aoju/bus/core/net/tls/HostnameVerifier.java index 53cc32d4c3..fba28e9028 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/secure/HostnameVerifier.java +++ b/bus-core/src/main/java/org/aoju/bus/core/net/tls/HostnameVerifier.java @@ -23,10 +23,10 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.http.secure; +package org.aoju.bus.core.net.tls; +import org.aoju.bus.core.lang.RegEx; import org.aoju.bus.core.lang.Symbol; -import org.aoju.bus.http.Builder; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSession; @@ -100,7 +100,7 @@ public boolean verify(String host, SSLSession session) { } public boolean verify(String host, X509Certificate certificate) { - return Builder.verifyAsIpAddress(host) + return RegEx.IP_ADDRESS.matcher(host).matches() ? verifyIpAddress(host, certificate) : verifyHostname(host, certificate); } diff --git a/bus-http/src/main/java/org/aoju/bus/http/secure/SSLContextBuilder.java b/bus-core/src/main/java/org/aoju/bus/core/net/tls/SSLContextBuilder.java similarity index 87% rename from bus-http/src/main/java/org/aoju/bus/http/secure/SSLContextBuilder.java rename to bus-core/src/main/java/org/aoju/bus/core/net/tls/SSLContextBuilder.java index f3cab6c3b5..c221c508b4 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/secure/SSLContextBuilder.java +++ b/bus-core/src/main/java/org/aoju/bus/core/net/tls/SSLContextBuilder.java @@ -23,19 +23,15 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.http.secure; +package org.aoju.bus.core.net.tls; import org.aoju.bus.core.builder.Builder; import org.aoju.bus.core.exception.InternalException; import org.aoju.bus.core.lang.Http; import org.aoju.bus.core.toolkit.ArrayKit; import org.aoju.bus.core.toolkit.StringKit; -import org.aoju.bus.http.accord.platform.Platform; -import javax.net.ssl.KeyManager; -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.*; import java.security.*; import java.util.Arrays; @@ -67,7 +63,7 @@ public class SSLContextBuilder implements Builder { * * @return SSLContextBuilder */ - public static SSLContextBuilder create() { + public static SSLContextBuilder of() { return new SSLContextBuilder(); } @@ -79,7 +75,7 @@ public static SSLContextBuilder create() { * @throws InternalException 包装 GeneralSecurityException异常 */ public static SSLContext createSSLContext(String protocol) throws InternalException { - return create().setProtocol(protocol).build(); + return of().setProtocol(protocol).build(); } /** @@ -108,12 +104,56 @@ public static SSLContext createSSLContext(String protocol, KeyManager keyManager * @throws InternalException 包装 GeneralSecurityException异常 */ public static SSLContext createSSLContext(String protocol, KeyManager[] keyManagers, TrustManager[] trustManagers) throws InternalException { - return SSLContextBuilder.create() + return SSLContextBuilder.of() .setProtocol(protocol) .setKeyManagers(keyManagers) .setTrustManagers(trustManagers).build(); } + /** + * 创建SSL证书 + * + * @param x509TrustManager 证书信息 + * @return SSLSocketFactory ssl socket工厂 + */ + public static SSLSocketFactory newSslSocketFactory(X509TrustManager x509TrustManager) { + try { + SSLContext sslContext = getSSLContext(); + sslContext.init(null, new TrustManager[]{x509TrustManager}, null); + return sslContext.getSocketFactory(); + } catch (GeneralSecurityException e) { + throw new AssertionError("No System TLS", e); // The system has no TLS. Just give up. + } + } + + public static SSLContext getSSLContext() { + try { + return SSLContext.getInstance(Http.TLS_V_13); + } catch (NoSuchAlgorithmException e) { + try { + return SSLContext.getInstance(Http.TLS); + } catch (NoSuchAlgorithmException e2) { + throw new IllegalStateException("No TLS provider", e); + } + } + } + + public static X509TrustManager newTrustManager() { + try { + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( + TrustManagerFactory.getDefaultAlgorithm()); + trustManagerFactory.init((KeyStore) null); + TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); + if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) { + throw new IllegalStateException("Unexpected default trust managers:" + + Arrays.toString(trustManagers)); + } + return (X509TrustManager) trustManagers[0]; + } catch (GeneralSecurityException e) { + throw new AssertionError("No System TLS", e); // The system has no TLS. Just give up. + } + } + /** * 设置协议。例如TLS等 * @@ -203,36 +243,4 @@ public SSLContext buildQuietly() throws InternalException { } } - /** - * 创建SSL证书 - * - * @param x509TrustManager 证书信息 - * @return SSLSocketFactory ssl socket工厂 - */ - public static javax.net.ssl.SSLSocketFactory newSslSocketFactory(javax.net.ssl.X509TrustManager x509TrustManager) { - try { - SSLContext sslContext = Platform.get().getSSLContext(); - sslContext.init(null, new TrustManager[]{x509TrustManager}, new SecureRandom()); - return sslContext.getSocketFactory(); - } catch (GeneralSecurityException ignored) { - throw new AssertionError("No System TLS", ignored); - } - } - - public static javax.net.ssl.X509TrustManager newTrustManager() { - try { - TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance( - TrustManagerFactory.getDefaultAlgorithm()); - trustManagerFactory.init((KeyStore) null); - TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); - if (trustManagers.length != 1 || !(trustManagers[0] instanceof javax.net.ssl.X509TrustManager)) { - throw new IllegalStateException("Unexpected default trust managers:" - + Arrays.toString(trustManagers)); - } - return (javax.net.ssl.X509TrustManager) trustManagers[0]; - } catch (GeneralSecurityException e) { - throw new AssertionError("No System TLS", e); - } - } - } diff --git a/bus-http/src/main/java/org/aoju/bus/http/secure/TlsVersion.java b/bus-core/src/main/java/org/aoju/bus/core/net/tls/TlsVersion.java similarity index 90% rename from bus-http/src/main/java/org/aoju/bus/http/secure/TlsVersion.java rename to bus-core/src/main/java/org/aoju/bus/core/net/tls/TlsVersion.java index 34ea73fa1b..4021b40b49 100644 --- a/bus-http/src/main/java/org/aoju/bus/http/secure/TlsVersion.java +++ b/bus-core/src/main/java/org/aoju/bus/core/net/tls/TlsVersion.java @@ -23,7 +23,9 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.http.secure; +package org.aoju.bus.core.net.tls; + +import org.aoju.bus.core.lang.Http; import java.util.ArrayList; import java.util.Collections; @@ -41,23 +43,23 @@ public enum TlsVersion { /** * 2016年版本 */ - TLS_1_3("TLSv1.3"), + TLS_1_3(Http.TLS_V_13), /** * 2008年版本 */ - TLS_1_2("TLSv1.2"), + TLS_1_2(Http.TLS_V_12), /** * 2006年版本 */ - TLS_1_1("TLSv1.1"), + TLS_1_1(Http.TLS_V_11), /** * 1999年版本 */ - TLS_1_0("TLSv1"), + TLS_1_0(Http.TLS_V_10), /** * 1996年版本 */ - SSL_3_0("SSLv3"); + SSL_3_0(Http.SSL_V_30); public final String javaName; @@ -67,15 +69,15 @@ public enum TlsVersion { public static TlsVersion forJavaName(String javaName) { switch (javaName) { - case "TLSv1.3": + case Http.TLS_V_13: return TLS_1_3; - case "TLSv1.2": + case Http.TLS_V_12: return TLS_1_2; - case "TLSv1.1": + case Http.TLS_V_11: return TLS_1_1; - case "TLSv1": + case Http.TLS_V_10: return TLS_1_0; - case "SSLv3": + case Http.SSL_V_30: return SSL_3_0; } throw new IllegalArgumentException("Unexpected TLS version: " + javaName); diff --git a/bus-core/src/main/java/org/aoju/bus/core/net/tls/package-info.java b/bus-core/src/main/java/org/aoju/bus/core/net/tls/package-info.java new file mode 100644 index 0000000000..d3be708dc6 --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/net/tls/package-info.java @@ -0,0 +1,7 @@ +/** + * SSL相关封装 + * + * @author Kimi Liu + * @since Java 17+ + */ +package org.aoju.bus.core.net.tls; diff --git a/bus-core/src/main/java/org/aoju/bus/core/scanner/AnnotationScanner.java b/bus-core/src/main/java/org/aoju/bus/core/scanner/AnnotationScanner.java index 8cf0092aa5..24e7c9abd3 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/scanner/AnnotationScanner.java +++ b/bus-core/src/main/java/org/aoju/bus/core/scanner/AnnotationScanner.java @@ -48,16 +48,6 @@ */ public interface AnnotationScanner { - /** - * 判断是否支持扫描该注解元素 - * - * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @return 是否支持扫描该注解元素 - */ - default boolean support(AnnotatedElement annotatedEle) { - return false; - } - /** * 给定一组扫描器,使用第一个支持处理该类型元素的扫描器获取元素上可能存在的注解 * @@ -77,43 +67,53 @@ static List scanByAnySupported(AnnotatedElement annotatedEle, Annota } /** - * 若{@link #support(AnnotatedElement)}返回{@code true}, - * 则调用并返回{@link #getAnnotations(AnnotatedElement)}结果, - * 否则返回{@link Collections#emptyList()} + * 根据指定的扫描器,扫描元素上可能存在的注解 * * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @param scanners 注解扫描器 * @return 注解 */ - default List getIfSupport(AnnotatedElement annotatedEle) { - return support(annotatedEle) ? getAnnotations(annotatedEle) : Collections.emptyList(); + static List scanByAllScanner(AnnotatedElement annotatedEle, AnnotationScanner... scanners) { + if (ObjectKit.isNull(annotatedEle) && ArrayKit.isNotEmpty(scanners)) { + return Collections.emptyList(); + } + return Stream.of(scanners) + .map(scanner -> scanner.getIfSupport(annotatedEle)) + .flatMap(Collection::stream) + .collect(Collectors.toList()); } /** - * 根据指定的扫描器,扫描元素上可能存在的注解 + * 判断是否支持扫描该注解元素 + * + * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @return 是否支持扫描该注解元素 + */ + default boolean support(AnnotatedElement annotatedEle) { + return false; + } + + /** + * 若{@link #support(AnnotatedElement)}返回{@code true}, + * 则调用并返回{@link #getAnnotations(AnnotatedElement)}结果, + * 否则返回{@link Collections#emptyList()} * * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @param scanners 注解扫描器 * @return 注解 */ - static List scanByAllScanner(AnnotatedElement annotatedEle, AnnotationScanner... scanners) { - if (ObjectKit.isNull(annotatedEle) && ArrayKit.isNotEmpty(scanners)) { - return Collections.emptyList(); - } - return Stream.of(scanners) - .map(scanner -> scanner.getIfSupport(annotatedEle)) - .flatMap(Collection::stream) - .collect(Collectors.toList()); + default List getIfSupport(AnnotatedElement annotatedEle) { + return support(annotatedEle) ? getAnnotations(annotatedEle) : Collections.emptyList(); } /** * 获取注解元素上的全部注解。调用该方法前,需要确保调用{@link #support(AnnotatedElement)}返回为true * * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @return 注解 - */ - default List getAnnotations(AnnotatedElement annotatedEle) { - final List annotations = new ArrayList<>(); - scan((index, annotation) -> annotations.add(annotation), annotatedEle, null); + * @return 注解 + */ + default List getAnnotations(AnnotatedElement annotatedEle) { + final List annotations = new ArrayList<>(); + scan((index, annotation) -> annotations.add(annotation), annotatedEle, null); return annotations; } @@ -123,12 +123,12 @@ default List getAnnotations(AnnotatedElement annotatedEle) { * * @param consumer 对获取到的注解和注解对应的层级索引的处理 * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @param filter 注解过滤器,无法通过过滤器的注解不会被处理。该参数允许为空。 - */ - default void scan(BiConsumer consumer, AnnotatedElement annotatedEle, Predicate filter) { - filter = ObjectKit.defaultIfNull(filter, annotation -> true); - for (final Annotation annotation : annotatedEle.getAnnotations()) { - if (AnnoKit.isNotJdkMateAnnotation(annotation.annotationType()) && filter.test(annotation)) { + * @param filter 注解过滤器,无法通过过滤器的注解不会被处理。该参数允许为空。 + */ + default void scan(BiConsumer consumer, AnnotatedElement annotatedEle, Predicate filter) { + filter = ObjectKit.defaultIfNull(filter, annotation -> true); + for (final Annotation annotation : annotatedEle.getAnnotations()) { + if (AnnoKit.isNotJdkMateAnnotation(annotation.annotationType()) && filter.test(annotation)) { consumer.accept(0, annotation); } } @@ -139,12 +139,12 @@ default void scan(BiConsumer consumer, AnnotatedElement ann * * @param consumer 对获取到的注解和注解对应的层级索引的处理 * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @param filter 注解过滤器,无法通过过滤器的注解不会被处理。该参数允许为空。 - */ - default void scanIfSupport(BiConsumer consumer, AnnotatedElement annotatedEle, Predicate filter) { - if (support(annotatedEle)) { - scan(consumer, annotatedEle, filter); - } - } + * @param filter 注解过滤器,无法通过过滤器的注解不会被处理。该参数允许为空。 + */ + default void scanIfSupport(BiConsumer consumer, AnnotatedElement annotatedEle, Predicate filter) { + if (support(annotatedEle)) { + scan(consumer, annotatedEle, filter); + } + } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/scanner/ClassScaner.java b/bus-core/src/main/java/org/aoju/bus/core/scanner/ClassScaner.java index d9b1d8ab78..4f8618869b 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/scanner/ClassScaner.java +++ b/bus-core/src/main/java/org/aoju/bus/core/scanner/ClassScaner.java @@ -76,11 +76,26 @@ public class ClassScaner { * 编码 */ private final java.nio.charset.Charset charset; - private final Set> classes = new HashSet<>(); /** * 是否初始化类 */ private boolean initialize; + /** + * 扫描结果集 + */ + private final Set> classes = new HashSet<>(); + /** + * 获取加载错误的类名列表 + */ + private final Set classesOfLoadError = new HashSet<>(); + /** + * 忽略loadClass时的错误 + */ + private boolean ignoreLoadError = false; + /** + * 类加载器 + */ + private ClassLoader classLoader; /** * 构造,默认UTF-8编码 @@ -125,7 +140,6 @@ public ClassScaner(String packageName, Predicate> predicate, java.nio.c this.charset = charset; } - /** * 扫描该包路径下所有class文件,包括其他加载的jar或者类 * @@ -140,16 +154,16 @@ public static Set> scanAllPackage() { * 如果包路径为 com.abs + A.class 但是输入 abs会产生classNotFoundException * 因为className 应该为 com.abs.A 现在却成为abs.A,此工具类对该异常进行忽略处理 * - * @param packageName 包路径 com | com.xxx + * @param packageName 包路径 com | com. | com.abs | com.abs. * @param classFilter class过滤器,过滤掉不需要的class * @return 类集合 */ - public static Set> scanAllPackage(String packageName, Predicate> classFilter) { + public static Set> scanAllPackage(final String packageName, final Predicate> classFilter) { return new ClassScaner(packageName, classFilter).scan(true); } /** - * 扫描该包路径下所有class文件 + * 扫描classpath下所有class文件,如果classpath下已经有类,不再扫描其他加载的jar或者类 * * @return 类集合 */ @@ -160,24 +174,24 @@ public static Set> scanPackage() { /** * 扫描该包路径下所有class文件 * - * @param packageName 包路径 com | com.xxx + * @param packageName 包路径 com | com. | com.abs | com.abs. * @return 类集合 */ - public static Set> scanPackage(String packageName) { + public static Set> scanPackage(final String packageName) { return scanPackage(packageName, null); } /** - * 扫面包路径下满足class过滤器条件的所有class文件, - * 如果包路径为 com.xxx + A.class 但是输入 abs会产生classNotFoundException - * 因为className 应该为 com.xxx.A 现在却成为xxx.A,此工具类对该异常进行忽略处理 + * 扫描包路径下满足class过滤器条件的所有class文件, + * 如果包路径为 com.abs + A.class 但是输入 abs会产生classNotFoundException + * 因为className 应该为 com.abs.A 现在却成为abs.A,此工具类对该异常进行忽略处理 * - * @param packageName 包路径 com | com.xxx - * @param predicate class过滤器,过滤掉不需要的class + * @param packageName 包路径 com | com. | com.abs | com.abs. + * @param classFilter class过滤器,过滤掉不需要的class * @return 类集合 */ - public static Set> scanPackage(String packageName, Predicate> predicate) { - return new ClassScaner(packageName, predicate).scan(); + public static Set> scanPackage(final String packageName, final Predicate> classFilter) { + return new ClassScaner(packageName, classFilter).scan(); } /** @@ -187,18 +201,19 @@ public static Set> scanPackage(String packageName, Predicate> * @param annotationClass 注解类 * @return 类集合 */ - public static Set> scanAllPackageByAnnotation(String packageName, Class annotationClass) { + public static Set> scanAllPackageByAnnotation(final String packageName, final Class annotationClass) { return scanAllPackage(packageName, clazz -> clazz.isAnnotationPresent(annotationClass)); } /** * 扫描指定包路径下所有包含指定注解的类 + * 如果classpath下已经有类,不再扫描其他加载的jar或者类 * * @param packageName 包路径 * @param annotationClass 注解类 * @return 类集合 */ - public static Set> scanPackageByAnnotation(String packageName, final Class annotationClass) { + public static Set> scanPackageByAnnotation(final String packageName, final Class annotationClass) { return scanPackage(packageName, clazz -> clazz.isAnnotationPresent(annotationClass)); } @@ -209,18 +224,19 @@ public static Set> scanPackageByAnnotation(String packageName, final Cl * @param superClass 父类或接口(不包括) * @return 类集合 */ - public static Set> scanAllPackageBySuper(String packageName, Class superClass) { + public static Set> scanAllPackageBySuper(final String packageName, final Class superClass) { return scanAllPackage(packageName, clazz -> superClass.isAssignableFrom(clazz) && !superClass.equals(clazz)); } /** - * 扫描指定包路径下所有指定类或接口的子类或实现类 + * 扫描指定包路径下所有指定类或接口的子类或实现类,不包括指定父类本身 + * 如果classpath下已经有类,不再扫描其他加载的jar或者类 * * @param packageName 包路径 - * @param superClass 父类或接口 + * @param superClass 父类或接口(不包括) * @return 类集合 */ - public static Set> scanPackageBySuper(String packageName, final Class superClass) { + public static Set> scanPackageBySuper(final String packageName, final Class superClass) { return scanPackage(packageName, clazz -> superClass.isAssignableFrom(clazz) && !superClass.equals(clazz)); } @@ -240,11 +256,15 @@ public Set> scan() { * @param forceScanJavaClassPaths 是否强制扫描其他位于classpath关联jar中的类 * @return 类集合 */ - public Set> scan(boolean forceScanJavaClassPaths) { - for (URL url : FileKit.getUrls(this.packagePath)) { + public Set> scan(final boolean forceScanJavaClassPaths) { + // 多次扫描时,清理上次扫描历史 + this.classes.clear(); + this.classesOfLoadError.clear(); + + for (final URL url : FileKit.getUrls(this.packagePath)) { switch (url.getProtocol()) { case "file": - scanFile(new File(UriKit.decode(url.getFile(), this.charset.name())), null); + scanFile(new File(UriKit.decode(url.getFile(), this.charset)), null); break; case "jar": scanJar(UriKit.getJarFile(url)); @@ -252,6 +272,7 @@ public Set> scan(boolean forceScanJavaClassPaths) { } } + // classpath下未找到,则扫描其他jar包下的类 if (forceScanJavaClassPaths || CollKit.isEmpty(this.classes)) { scanJavaClassPaths(); } @@ -260,9 +281,43 @@ public Set> scan(boolean forceScanJavaClassPaths) { } /** - * 扫描Java指定的ClassPath路径 + * 忽略加载错误扫描后,可以获得之前扫描时加载错误的类名字集合 + * + * @return 加载错误的类名字集合 + */ + public Set getClassesOfLoadError() { + return Collections.unmodifiableSet(this.classesOfLoadError); + } + + /** + * 设置是否忽略所有错误 + * + * @param ignoreLoadError 是否忽略错误 + */ + public void setIgnoreLoadError(final boolean ignoreLoadError) { + this.ignoreLoadError = ignoreLoadError; + } + + /** + * 设置是否在扫描到类时初始化类 + * + * @param initialize 是否初始化类 + */ + public void setInitialize(final boolean initialize) { + this.initialize = initialize; + } + + /** + * 设置自定义的类加载器 * - * @return 扫描到的类 + * @param classLoader 类加载器 + */ + public void setClassLoader(final ClassLoader classLoader) { + this.classLoader = classLoader; + } + + /** + * 扫描Java指定的ClassPath路径 */ private void scanJavaClassPaths() { final String[] javaClassPaths = ClassKit.getJavaClassPaths(); @@ -280,7 +335,7 @@ private void scanJavaClassPaths() { * @param file 文件或目录 * @param rootDir 包名对应classpath绝对路径 */ - private void scanFile(File file, String rootDir) { + private void scanFile(final File file, final String rootDir) { if (file.isFile()) { final String fileName = file.getAbsolutePath(); if (fileName.endsWith(FileType.CLASS)) { @@ -300,7 +355,7 @@ private void scanFile(File file, String rootDir) { } else if (file.isDirectory()) { final File[] files = file.listFiles(); if (null != files) { - for (File subFile : files) { + for (final File subFile : files) { scanFile(subFile, (null == rootDir) ? subPathBeforePackage(file) : rootDir); } } @@ -312,13 +367,13 @@ private void scanFile(File file, String rootDir) { * * @param jar jar包 */ - private void scanJar(JarFile jar) { + private void scanJar(final JarFile jar) { String name; - for (JarEntry entry : new EnumerationIterator<>(jar.entries())) { + for (final JarEntry entry : new EnumerationIterator<>(jar.entries())) { name = StringKit.removePrefix(entry.getName(), Symbol.SLASH); if (StringKit.isEmpty(packagePath) || name.startsWith(this.packagePath)) { if (name.endsWith(FileType.CLASS) && false == entry.isDirectory()) { - final String className = name + final String className = name// .substring(0, name.length() - 6) .replace(Symbol.C_SLASH, Symbol.C_DOT); addIfAccept(loadClass(className)); @@ -327,55 +382,57 @@ private void scanJar(JarFile jar) { } } - /** - * 设置是否在扫描到类时初始化类 - * - * @param initialize 是否初始化类 - */ - public void setInitialize(boolean initialize) { - this.initialize = initialize; - } - /** * 加载类 * * @param className 类名 * @return 加载的类 */ - private Class loadClass(String className) { + protected Class loadClass(final String className) { + ClassLoader loader = this.classLoader; + if (null == loader) { + loader = ClassKit.getClassLoader(); + this.classLoader = loader; + } + Class clazz = null; try { - clazz = Class.forName(className, this.initialize, ClassKit.getClassLoader()); - } catch (NoClassDefFoundError e) { - // 由于依赖库导致的类无法加载,直接跳过此类 - } catch (UnsupportedClassVersionError | ClassNotFoundException ee) { - // 版本导致的不兼容的类,跳过 - } catch (Exception e) { - throw new RuntimeException(e); + clazz = Class.forName(className, this.initialize, loader); + } catch (final NoClassDefFoundError | ClassNotFoundException e) { + // 由于依赖库导致的类无法加载,直接跳过此类 + } catch (final UnsupportedClassVersionError e) { + // 版本导致的不兼容的类,跳过 + } catch (final Exception e) { + classesOfLoadError.add(className); + } catch (final Throwable e) { + if (false == this.ignoreLoadError) { + throw new RuntimeException(e); + } else { + classesOfLoadError.add(className); + } } return clazz; } /** - * 通过过滤器,是否满足接受此类的条件 + * 通过过滤器,是否满足接受此类的条件 * - * @param className 类 - * @return 是否接受 + * @param className 类名 */ - private void addIfAccept(String className) { + private void addIfAccept(final String className) { if (StringKit.isBlank(className)) { return; } - int classLen = className.length(); - int packageLen = this.packageName.length(); + final int classLen = className.length(); + final int packageLen = this.packageName.length(); if (classLen == packageLen) { //类名和包名长度一致,用户可能传入的包名是类名 if (className.equals(this.packageName)) { addIfAccept(loadClass(className)); } } else if (classLen > packageLen) { - //检查类名是否以指定包名为前缀,包名后加. - if (className.startsWith(this.packageNameWithDot)) { + //检查类名是否以指定包名为前缀,包名后加.(org.aoju.c和org.aoju.b这类类名引起的歧义) + if (Symbol.DOT.equals(this.packageNameWithDot) || className.startsWith(this.packageNameWithDot)) { addIfAccept(loadClass(className)); } } @@ -385,9 +442,8 @@ private void addIfAccept(String className) { * 通过过滤器,是否满足接受此类的条件 * * @param clazz 类 - * @return 是否接受 */ - private void addIfAccept(Class clazz) { + private void addIfAccept(final Class clazz) { if (null != clazz) { Predicate> classFilter = this.predicate; if (null == classFilter || classFilter.test(clazz)) { @@ -402,7 +458,7 @@ private void addIfAccept(Class clazz) { * @param file 文件 * @return 包名之前的部分 */ - private String subPathBeforePackage(File file) { + private String subPathBeforePackage(final File file) { String filePath = file.getAbsolutePath(); if (StringKit.isNotEmpty(this.packageDirName)) { filePath = StringKit.subBefore(filePath, this.packageDirName, true); diff --git a/bus-core/src/main/java/org/aoju/bus/core/scanner/SynthesizedProcessor.java b/bus-core/src/main/java/org/aoju/bus/core/scanner/SynthesizedProcessor.java index 2f78a96052..956c9b2e2d 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/scanner/SynthesizedProcessor.java +++ b/bus-core/src/main/java/org/aoju/bus/core/scanner/SynthesizedProcessor.java @@ -36,15 +36,15 @@ @FunctionalInterface public interface SynthesizedProcessor { - /** - * 从一批被合成注解中,获取指定名称与类型的属性值 - * - * @param attributeName 属性名称 - * @param attributeType 属性类型 - * @param synthesizedAnnotations 被合成的注解 - * @param 属性类型 - * @return 属性值 - */ - R getAttributeValue(String attributeName, Class attributeType, Collection synthesizedAnnotations); + /** + * 从一批被合成注解中,获取指定名称与类型的属性值 + * + * @param attributeName 属性名称 + * @param attributeType 属性类型 + * @param synthesizedAnnotations 被合成的注解 + * @param 属性类型 + * @return 属性值 + */ + R getAttributeValue(String attributeName, Class attributeType, Collection synthesizedAnnotations); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/scanner/SyntheticProxy.java b/bus-core/src/main/java/org/aoju/bus/core/scanner/SyntheticProxy.java index f8ab20c834..c118d56073 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/scanner/SyntheticProxy.java +++ b/bus-core/src/main/java/org/aoju/bus/core/scanner/SyntheticProxy.java @@ -47,113 +47,113 @@ */ class SyntheticProxy implements InvocationHandler { - private final Synthetic synthetic; - private final Synthesized annotation; - private final Map> methods; - - SyntheticProxy(Synthetic synthetic, Synthesized annotation) { - this.synthetic = synthetic; - this.annotation = annotation; - this.methods = new HashMap<>(9); - loadMethods(); - } - - /** - * 创建一个代理注解,生成的代理对象将是{@link Proxys}与指定的注解类的子类。 - *
    - *
  • 当作为{@code annotationType}所指定的类型使用时,其属性将通过合成它的{@link Synthetic}获取;
  • - *
  • 当作为{@link Proxys}或{@link Synthesized}使用时,将可以获得原始注解实例的相关信息;
  • - *
- * - * @param annotationType 注解类型 - * @param synthetic 合成注解 - * @return 代理注解 - */ - static T create( - Class annotationType, Synthetic synthetic) { - final Synthesized annotation = synthetic.getSynthesizedAnnotation(annotationType); - final org.aoju.bus.core.scanner.SyntheticProxy proxyHandler = new org.aoju.bus.core.scanner.SyntheticProxy(synthetic, annotation); - if (ObjectKit.isNull(annotation)) { - return null; - } - return (T) Proxy.newProxyInstance( - annotationType.getClassLoader(), - new Class[]{annotationType, Proxys.class}, - proxyHandler - ); - } - - static boolean isProxyAnnotation(Class targetClass) { - return ClassKit.isAssignable(Proxys.class, targetClass); - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - return Optional.ofNullable(methods.get(method.getName())) - .map(m -> m.apply(method, args)) - .orElseGet(() -> ReflectKit.invoke(this, method, args)); - } - - void loadMethods() { - methods.put("toString", (method, args) -> proxyToString()); - methods.put("hashCode", (method, args) -> proxyHashCode()); - methods.put("getSyntheticAnnotation", (method, args) -> proxyGetSyntheticAnnotation()); - methods.put("getSynthesizedAnnotation", (method, args) -> proxyGetSynthesizedAnnotation()); - methods.put("getRoot", (method, args) -> annotation.getRoot()); - methods.put("isRoot", (method, args) -> annotation.isRoot()); - methods.put("getVerticalDistance", (method, args) -> annotation.getVerticalDistance()); - methods.put("getHorizontalDistance", (method, args) -> annotation.getHorizontalDistance()); - methods.put("hasAttribute", (method, args) -> annotation.hasAttribute((String) args[0], (Class) args[1])); - methods.put("getAttribute", (method, args) -> annotation.getAttribute((String) args[0])); - methods.put("annotationType", (method, args) -> annotation.annotationType()); - for (final Method declaredMethod : annotation.getAnnotation().annotationType().getDeclaredMethods()) { - methods.put(declaredMethod.getName(), (method, args) -> proxyAttributeValue(method)); - } - } - - private String proxyToString() { - final String attributes = Stream.of(annotation.annotationType().getDeclaredMethods()) - .filter(AnnoKit::isAttributeMethod) - .map(method -> StringKit.format("{}={}", method.getName(), synthetic.getAttribute(method.getName(), method.getReturnType()))) - .collect(Collectors.joining(", ")); - return StringKit.format("@{}({})", annotation.annotationType().getName(), attributes); - } - - private int proxyHashCode() { - return Objects.hash(synthetic, annotation); - } - - private Object proxyGetSyntheticAnnotation() { - return synthetic; - } - - private Object proxyGetSynthesizedAnnotation() { - return annotation; - } - - private Object proxyAttributeValue(Method attributeMethod) { - return synthetic.getAttribute(attributeMethod.getName(), attributeMethod.getReturnType()); - } - - /** - * 通过代理类生成的合成注解 - */ - interface Proxys extends Synthesized { - - /** - * 获取该注解所属的合成注解 - * - * @return 合成注解 - */ - Synthetic getSyntheticAnnotation(); - - /** - * 获取该代理注解对应的已合成注解 - * - * @return 理注解对应的已合成注解 - */ - Synthesized getSynthesizedAnnotation(); - - } + private final Synthetic synthetic; + private final Synthesized annotation; + private final Map> methods; + + SyntheticProxy(Synthetic synthetic, Synthesized annotation) { + this.synthetic = synthetic; + this.annotation = annotation; + this.methods = new HashMap<>(9); + loadMethods(); + } + + /** + * 创建一个代理注解,生成的代理对象将是{@link Proxys}与指定的注解类的子类。 + *
    + *
  • 当作为{@code annotationType}所指定的类型使用时,其属性将通过合成它的{@link Synthetic}获取;
  • + *
  • 当作为{@link Proxys}或{@link Synthesized}使用时,将可以获得原始注解实例的相关信息;
  • + *
+ * + * @param annotationType 注解类型 + * @param synthetic 合成注解 + * @return 代理注解 + */ + static T create( + Class annotationType, Synthetic synthetic) { + final Synthesized annotation = synthetic.getSynthesizedAnnotation(annotationType); + final org.aoju.bus.core.scanner.SyntheticProxy proxyHandler = new org.aoju.bus.core.scanner.SyntheticProxy(synthetic, annotation); + if (ObjectKit.isNull(annotation)) { + return null; + } + return (T) Proxy.newProxyInstance( + annotationType.getClassLoader(), + new Class[]{annotationType, Proxys.class}, + proxyHandler + ); + } + + static boolean isProxyAnnotation(Class targetClass) { + return ClassKit.isAssignable(Proxys.class, targetClass); + } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + return Optional.ofNullable(methods.get(method.getName())) + .map(m -> m.apply(method, args)) + .orElseGet(() -> ReflectKit.invoke(this, method, args)); + } + + void loadMethods() { + methods.put("toString", (method, args) -> proxyToString()); + methods.put("hashCode", (method, args) -> proxyHashCode()); + methods.put("getSyntheticAnnotation", (method, args) -> proxyGetSyntheticAnnotation()); + methods.put("getSynthesizedAnnotation", (method, args) -> proxyGetSynthesizedAnnotation()); + methods.put("getRoot", (method, args) -> annotation.getRoot()); + methods.put("isRoot", (method, args) -> annotation.isRoot()); + methods.put("getVerticalDistance", (method, args) -> annotation.getVerticalDistance()); + methods.put("getHorizontalDistance", (method, args) -> annotation.getHorizontalDistance()); + methods.put("hasAttribute", (method, args) -> annotation.hasAttribute((String) args[0], (Class) args[1])); + methods.put("getAttribute", (method, args) -> annotation.getAttribute((String) args[0])); + methods.put("annotationType", (method, args) -> annotation.annotationType()); + for (final Method declaredMethod : annotation.getAnnotation().annotationType().getDeclaredMethods()) { + methods.put(declaredMethod.getName(), (method, args) -> proxyAttributeValue(method)); + } + } + + private String proxyToString() { + final String attributes = Stream.of(annotation.annotationType().getDeclaredMethods()) + .filter(AnnoKit::isAttributeMethod) + .map(method -> StringKit.format("{}={}", method.getName(), synthetic.getAttribute(method.getName(), method.getReturnType()))) + .collect(Collectors.joining(", ")); + return StringKit.format("@{}({})", annotation.annotationType().getName(), attributes); + } + + private int proxyHashCode() { + return Objects.hash(synthetic, annotation); + } + + private Object proxyGetSyntheticAnnotation() { + return synthetic; + } + + private Object proxyGetSynthesizedAnnotation() { + return annotation; + } + + private Object proxyAttributeValue(Method attributeMethod) { + return synthetic.getAttribute(attributeMethod.getName(), attributeMethod.getReturnType()); + } + + /** + * 通过代理类生成的合成注解 + */ + interface Proxys extends Synthesized { + + /** + * 获取该注解所属的合成注解 + * + * @return 合成注解 + */ + Synthetic getSyntheticAnnotation(); + + /** + * 获取该代理注解对应的已合成注解 + * + * @return 理注解对应的已合成注解 + */ + Synthesized getSynthesizedAnnotation(); + + } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/AbstractTypeScanner.java b/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/AbstractTypeScanner.java index eab9300a2a..79229aafb4 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/AbstractTypeScanner.java +++ b/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/AbstractTypeScanner.java @@ -57,10 +57,10 @@ public abstract class AbstractTypeScanner> impl */ private final List>> converters; /** - * 当前实例 - */ - private final T typedThis; - /** + * 当前实例 + */ + private final T typedThis; + /** * 是否允许扫描父接口 */ private boolean includeInterfaces; @@ -87,71 +87,23 @@ public abstract class AbstractTypeScanner> impl * @param excludeTypes 不包含的类型 */ protected AbstractTypeScanner(boolean includeSupperClass, boolean includeInterfaces, Predicate> filter, Set> excludeTypes) { - Assert.notNull(filter, "filter must not null"); - Assert.notNull(excludeTypes, "excludeTypes must not null"); - this.includeSupperClass = includeSupperClass; - this.includeInterfaces = includeInterfaces; - this.filter = filter; - this.excludeTypes = excludeTypes; - this.converters = new ArrayList<>(); - this.typedThis = (T) this; - } - - /** - * 是否允许扫描父类 - * - * @return 是否允许扫描父类 - */ - public boolean isIncludeSupperClass() { - return includeSupperClass; - } - - /** - * 是否允许扫描父接口 - * - * @return 是否允许扫描父接口 - */ - public boolean isIncludeInterfaces() { - return includeInterfaces; - } - - /** - * 设置过滤器,若类型无法通过该过滤器,则该类型及其树结构将直接不被查找 - * - * @param filter 过滤器 - * @return 当前实例 - */ - public T setFilter(Predicate> filter) { - Assert.notNull(filter, "filter must not null"); - this.filter = filter; - return typedThis; - } - - /** - * 添加不扫描的类型,该类型及其树结构将直接不被查找 - * - * @param excludeTypes 不扫描的类型 - * @return 当前实例 - */ - public T addExcludeTypes(Class... excludeTypes) { - CollKit.addAll(this.excludeTypes, excludeTypes); - return typedThis; - } + Assert.notNull(filter, "filter must not null"); + Assert.notNull(excludeTypes, "excludeTypes must not null"); + this.includeSupperClass = includeSupperClass; + this.includeInterfaces = includeInterfaces; + this.filter = filter; + this.excludeTypes = excludeTypes; + this.converters = new ArrayList<>(); + this.typedThis = (T) this; + } - /** - * 添加转换器 - * - * @param converter 转换器 - * @return 当前实例 - * @see JdkProxyClassConverter + /** + * 是否允许扫描父类 + * + * @return 是否允许扫描父类 */ - public T addConverters(UnaryOperator> converter) { - Assert.notNull(converter, "converter must not null"); - this.converters.add(converter); - if (!this.hasConverters) { - this.hasConverters = CollKit.isNotEmpty(this.converters); - } - return typedThis; + public boolean isIncludeSupperClass() { + return includeSupperClass; } /** @@ -165,6 +117,15 @@ protected T setIncludeSupperClass(boolean includeSupperClass) { return typedThis; } + /** + * 是否允许扫描父接口 + * + * @return 是否允许扫描父接口 + */ + public boolean isIncludeInterfaces() { + return includeInterfaces; + } + /** * 是否允许扫描父接口 * @@ -176,6 +137,45 @@ protected T setIncludeInterfaces(boolean includeInterfaces) { return typedThis; } + /** + * 设置过滤器,若类型无法通过该过滤器,则该类型及其树结构将直接不被查找 + * + * @param filter 过滤器 + * @return 当前实例 + */ + public T setFilter(Predicate> filter) { + Assert.notNull(filter, "filter must not null"); + this.filter = filter; + return typedThis; + } + + /** + * 添加不扫描的类型,该类型及其树结构将直接不被查找 + * + * @param excludeTypes 不扫描的类型 + * @return 当前实例 + */ + public T addExcludeTypes(Class... excludeTypes) { + CollKit.addAll(this.excludeTypes, excludeTypes); + return typedThis; + } + + /** + * 添加转换器 + * + * @param converter 转换器 + * @return 当前实例 + * @see JdkProxyClassConverter + */ + public T addConverters(UnaryOperator> converter) { + Assert.notNull(converter, "converter must not null"); + this.converters.add(converter); + if (!this.hasConverters) { + this.hasConverters = CollKit.isNotEmpty(this.converters); + } + return typedThis; + } + /** * 则根据广度优先递归扫描类的层级结构,并对层级结构中类/接口声明的层级索引和它们声明的注解对象进行处理 * @@ -185,59 +185,59 @@ protected T setIncludeInterfaces(boolean includeInterfaces) { */ @Override public void scan(BiConsumer consumer, AnnotatedElement annotatedEle, Predicate filter) { - filter = ObjectKit.defaultIfNull(filter, annotation -> true); - final Class sourceClass = getClassFormAnnotatedElement(annotatedEle); - final Deque>> classDeque = CollKit.newLinkedList(CollKit.newArrayList(sourceClass)); - final Set> accessedTypes = new LinkedHashSet<>(); - int index = 0; - while (!classDeque.isEmpty()) { - final List> currClassQueue = classDeque.removeFirst(); - final List> nextClassQueue = new ArrayList<>(); - for (Class targetClass : currClassQueue) { - targetClass = convert(targetClass); - // 过滤不需要处理的类 - if (isNotNeedProcess(accessedTypes, targetClass)) { - continue; - } - accessedTypes.add(targetClass); - // 扫描父类 - scanSuperClassIfNecessary(nextClassQueue, targetClass); - // 扫描接口 - scanInterfaceIfNecessary(nextClassQueue, targetClass); - // 处理层级索引和注解 - final Annotation[] targetAnnotations = getAnnotationsFromTargetClass(annotatedEle, index, targetClass); - for (final Annotation annotation : targetAnnotations) { - if (AnnoKit.isNotJdkMateAnnotation(annotation.annotationType()) || filter.test(annotation)) { - consumer.accept(index, annotation); - } - } - index++; - } - if (CollKit.isNotEmpty(nextClassQueue)) { - classDeque.addLast(nextClassQueue); - } - } - } + filter = ObjectKit.defaultIfNull(filter, annotation -> true); + final Class sourceClass = getClassFormAnnotatedElement(annotatedEle); + final Deque>> classDeque = CollKit.newLinkedList(CollKit.newArrayList(sourceClass)); + final Set> accessedTypes = new LinkedHashSet<>(); + int index = 0; + while (!classDeque.isEmpty()) { + final List> currClassQueue = classDeque.removeFirst(); + final List> nextClassQueue = new ArrayList<>(); + for (Class targetClass : currClassQueue) { + targetClass = convert(targetClass); + // 过滤不需要处理的类 + if (isNotNeedProcess(accessedTypes, targetClass)) { + continue; + } + accessedTypes.add(targetClass); + // 扫描父类 + scanSuperClassIfNecessary(nextClassQueue, targetClass); + // 扫描接口 + scanInterfaceIfNecessary(nextClassQueue, targetClass); + // 处理层级索引和注解 + final Annotation[] targetAnnotations = getAnnotationsFromTargetClass(annotatedEle, index, targetClass); + for (final Annotation annotation : targetAnnotations) { + if (AnnoKit.isNotJdkMateAnnotation(annotation.annotationType()) || filter.test(annotation)) { + consumer.accept(index, annotation); + } + } + index++; + } + if (CollKit.isNotEmpty(nextClassQueue)) { + classDeque.addLast(nextClassQueue); + } + } + } - /** - * 从要搜索的注解元素上获得要递归的类型 - * - * @param annotatedElement 注解元素 - * @return 要递归的类型 - */ - protected abstract Class getClassFormAnnotatedElement(AnnotatedElement annotatedElement); + /** + * 从要搜索的注解元素上获得要递归的类型 + * + * @param annotatedElement 注解元素 + * @return 要递归的类型 + */ + protected abstract Class getClassFormAnnotatedElement(AnnotatedElement annotatedElement); - /** - * 从类上获取最终所需的目标注解 - * - * @param source 最初的注解元素 - * @param index 类的层级索引 - * @param targetClass 类 - * @return 最终所需的目标注解 - */ - protected abstract Annotation[] getAnnotationsFromTargetClass(AnnotatedElement source, int index, Class targetClass); + /** + * 从类上获取最终所需的目标注解 + * + * @param source 最初的注解元素 + * @param index 类的层级索引 + * @param targetClass 类 + * @return 最终所需的目标注解 + */ + protected abstract Annotation[] getAnnotationsFromTargetClass(AnnotatedElement source, int index, Class targetClass); - /** + /** * 当前类是否不需要处理 * * @param accessedTypes 访问类型 @@ -281,29 +281,29 @@ protected void scanSuperClassIfNecessary(List> nextClassQueue, Class } } - /** - * 若存在转换器,则使用转换器对目标类进行转换 - * - * @param target 目标类 - * @return 转换后的类 - */ - protected Class convert(Class target) { - if (hasConverters) { - for (final UnaryOperator> converter : converters) { - target = converter.apply(target); - } - } - return target; - } + /** + * 若存在转换器,则使用转换器对目标类进行转换 + * + * @param target 目标类 + * @return 转换后的类 + */ + protected Class convert(Class target) { + if (hasConverters) { + for (final UnaryOperator> converter : converters) { + target = converter.apply(target); + } + } + return target; + } - /** - * 若类型为jdk代理类,则尝试转换为原始被代理类 - */ - public static class JdkProxyClassConverter implements UnaryOperator> { - @Override - public Class apply(Class sourceClass) { - return Proxy.isProxyClass(sourceClass) ? apply(sourceClass.getSuperclass()) : sourceClass; - } - } + /** + * 若类型为jdk代理类,则尝试转换为原始被代理类 + */ + public static class JdkProxyClassConverter implements UnaryOperator> { + @Override + public Class apply(Class sourceClass) { + return Proxy.isProxyClass(sourceClass) ? apply(sourceClass.getSuperclass()) : sourceClass; + } + } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/MetaScanner.java b/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/MetaScanner.java index 6a06d6fe9f..8f57635be6 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/MetaScanner.java +++ b/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/MetaScanner.java @@ -44,85 +44,86 @@ /** * 扫描注解类上存在的注解,支持处理枚举实例或枚举类型 * 需要注意,当待解析是枚举类时,有可能与{@link TypeScanner}冲突 + * * @author Kimi Liu * @since Java 17+ */ public class MetaScanner implements AnnotationScanner { - /** - * 获取当前注解的元注解后,是否继续递归扫描的元注解的元注解 - */ - private final boolean includeSupperMetaAnnotation; + /** + * 获取当前注解的元注解后,是否继续递归扫描的元注解的元注解 + */ + private final boolean includeSupperMetaAnnotation; - /** - * 构造一个元注解扫描器 - * - * @param includeSupperMetaAnnotation 获取当前注解的元注解后,是否继续递归扫描的元注解的元注解 - */ - public MetaScanner(boolean includeSupperMetaAnnotation) { - this.includeSupperMetaAnnotation = includeSupperMetaAnnotation; - } + /** + * 构造一个元注解扫描器 + * + * @param includeSupperMetaAnnotation 获取当前注解的元注解后,是否继续递归扫描的元注解的元注解 + */ + public MetaScanner(boolean includeSupperMetaAnnotation) { + this.includeSupperMetaAnnotation = includeSupperMetaAnnotation; + } - /** - * 构造一个元注解扫描器,默认在扫描当前注解上的元注解后,并继续递归扫描元注解 - */ - public MetaScanner() { - this(true); - } + /** + * 构造一个元注解扫描器,默认在扫描当前注解上的元注解后,并继续递归扫描元注解 + */ + public MetaScanner() { + this(true); + } - /** - * 判断是否支持扫描该注解元素,仅当注解元素是{@link Annotation}接口的子类{@link Class}时返回{@code true} - * - * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @return 是否支持扫描该注解元素 - */ - @Override - public boolean support(AnnotatedElement annotatedEle) { - return (annotatedEle instanceof Class && ClassKit.isAssignable(Annotation.class, (Class) annotatedEle)); - } + /** + * 判断是否支持扫描该注解元素,仅当注解元素是{@link Annotation}接口的子类{@link Class}时返回{@code true} + * + * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @return 是否支持扫描该注解元素 + */ + @Override + public boolean support(AnnotatedElement annotatedEle) { + return (annotatedEle instanceof Class && ClassKit.isAssignable(Annotation.class, (Class) annotatedEle)); + } - /** - * 获取注解元素上的全部注解。调用该方法前,需要确保调用{@link #support(AnnotatedElement)}返回为true - * - * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @return 注解 - */ - @Override - public List getAnnotations(AnnotatedElement annotatedEle) { - final List annotations = new ArrayList<>(); - scan( - (index, annotation) -> annotations.add(annotation), annotatedEle, + /** + * 获取注解元素上的全部注解。调用该方法前,需要确保调用{@link #support(AnnotatedElement)}返回为true + * + * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @return 注解 + */ + @Override + public List getAnnotations(AnnotatedElement annotatedEle) { + final List annotations = new ArrayList<>(); + scan( + (index, annotation) -> annotations.add(annotation), annotatedEle, annotation -> ObjectKit.notEquals(annotation, annotatedEle) - ); - return annotations; - } + ); + return annotations; + } - /** - * 按广度优先扫描指定注解上的元注解,对扫描到的注解与层级索引进行操作 - * - * @param consumer 当前层级索引与操作 - * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @param filter 过滤器 - */ - @Override - public void scan(BiConsumer consumer, AnnotatedElement annotatedEle, Predicate filter) { - filter = ObjectKit.defaultIfNull(filter, t -> true); - final Deque>> deque = CollKit.newLinkedList(CollKit.newArrayList((Class) annotatedEle)); - int distance = 0; - do { - final List> annotationTypes = deque.removeFirst(); - for (final Class type : annotationTypes) { - final List metaAnnotations = Stream.of(type.getAnnotations()) - .filter(a -> !AnnoKit.isJdkMetaAnnotation(a.annotationType())) - .filter(filter) - .collect(Collectors.toList()); - for (final Annotation metaAnnotation : metaAnnotations) { - consumer.accept(distance, metaAnnotation); - } - deque.addLast(CollKit.toList(metaAnnotations, Annotation::annotationType)); - } - distance++; - } while (includeSupperMetaAnnotation && !deque.isEmpty()); - } + /** + * 按广度优先扫描指定注解上的元注解,对扫描到的注解与层级索引进行操作 + * + * @param consumer 当前层级索引与操作 + * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @param filter 过滤器 + */ + @Override + public void scan(BiConsumer consumer, AnnotatedElement annotatedEle, Predicate filter) { + filter = ObjectKit.defaultIfNull(filter, t -> true); + final Deque>> deque = CollKit.newLinkedList(CollKit.newArrayList((Class) annotatedEle)); + int distance = 0; + do { + final List> annotationTypes = deque.removeFirst(); + for (final Class type : annotationTypes) { + final List metaAnnotations = Stream.of(type.getAnnotations()) + .filter(a -> !AnnoKit.isJdkMetaAnnotation(a.annotationType())) + .filter(filter) + .collect(Collectors.toList()); + for (final Annotation metaAnnotation : metaAnnotations) { + consumer.accept(distance, metaAnnotation); + } + deque.addLast(CollKit.toList(metaAnnotations, Annotation::annotationType)); + } + distance++; + } while (includeSupperMetaAnnotation && !deque.isEmpty()); + } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/MethodScanner.java b/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/MethodScanner.java index 453b730c82..ad066ba836 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/MethodScanner.java +++ b/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/MethodScanner.java @@ -46,103 +46,103 @@ */ public class MethodScanner extends AbstractTypeScanner implements AnnotationScanner { - /** - * 构造一个方法注解扫描器 - * - * @param scanSameSignatureMethod 是否扫描类层级结构中具有相同方法签名的方法 - * @param filter 过滤器 - * @param excludeTypes 不包含的类型 - */ - public MethodScanner(boolean scanSameSignatureMethod, Predicate> filter, Set> excludeTypes) { - super(scanSameSignatureMethod, scanSameSignatureMethod, filter, excludeTypes); - } + /** + * 构造一个方法注解扫描器 + * + * @param scanSameSignatureMethod 是否扫描类层级结构中具有相同方法签名的方法 + * @param filter 过滤器 + * @param excludeTypes 不包含的类型 + */ + public MethodScanner(boolean scanSameSignatureMethod, Predicate> filter, Set> excludeTypes) { + super(scanSameSignatureMethod, scanSameSignatureMethod, filter, excludeTypes); + } - /** - * 构造一个类注解扫描器 - * - * @param scanSameSignatureMethod 是否扫描类层级结构中具有相同方法签名的方法 - */ - public MethodScanner(boolean scanSameSignatureMethod) { - this(scanSameSignatureMethod, targetClass -> true, CollKit.newLinkedHashSet()); - } + /** + * 构造一个类注解扫描器 + * + * @param scanSameSignatureMethod 是否扫描类层级结构中具有相同方法签名的方法 + */ + public MethodScanner(boolean scanSameSignatureMethod) { + this(scanSameSignatureMethod, targetClass -> true, CollKit.newLinkedHashSet()); + } - /** - * 构造一个类注解扫描器,仅扫描该方法上直接声明的注解 - */ - public MethodScanner() { - this(false); - } + /** + * 构造一个类注解扫描器,仅扫描该方法上直接声明的注解 + */ + public MethodScanner() { + this(false); + } - /** - * 判断是否支持扫描该注解元素,仅当注解元素是{@link Method}时返回{@code true} - * - * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @return boolean 是否支持扫描该注解元素 - */ - @Override - public boolean support(AnnotatedElement annotatedEle) { - return annotatedEle instanceof Method; - } + /** + * 判断是否支持扫描该注解元素,仅当注解元素是{@link Method}时返回{@code true} + * + * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @return boolean 是否支持扫描该注解元素 + */ + @Override + public boolean support(AnnotatedElement annotatedEle) { + return annotatedEle instanceof Method; + } - /** - * 获取声明该方法的类 - * - * @param annotatedElement 注解元素 - * @return 要递归的类型 - * @see Method#getDeclaringClass() - */ - @Override - protected Class getClassFormAnnotatedElement(AnnotatedElement annotatedElement) { - return ((Method) annotatedElement).getDeclaringClass(); - } + /** + * 获取声明该方法的类 + * + * @param annotatedElement 注解元素 + * @return 要递归的类型 + * @see Method#getDeclaringClass() + */ + @Override + protected Class getClassFormAnnotatedElement(AnnotatedElement annotatedElement) { + return ((Method) annotatedElement).getDeclaringClass(); + } - /** - * 若父类/父接口中方法具有相同的方法签名,则返回该方法上的注解 - * - * @param source 原始方法 - * @param index 类的层级索引 - * @param targetClass 类 - * @return 最终所需的目标注解 - */ - @Override - protected Annotation[] getAnnotationsFromTargetClass(AnnotatedElement source, int index, Class targetClass) { - final Method sourceMethod = (Method) source; - return Stream.of(targetClass.getDeclaredMethods()) - .filter(superMethod -> !superMethod.isBridge()) - .filter(superMethod -> hasSameSignature(sourceMethod, superMethod)) - .map(AnnotatedElement::getAnnotations) - .flatMap(Stream::of) - .toArray(Annotation[]::new); - } + /** + * 若父类/父接口中方法具有相同的方法签名,则返回该方法上的注解 + * + * @param source 原始方法 + * @param index 类的层级索引 + * @param targetClass 类 + * @return 最终所需的目标注解 + */ + @Override + protected Annotation[] getAnnotationsFromTargetClass(AnnotatedElement source, int index, Class targetClass) { + final Method sourceMethod = (Method) source; + return Stream.of(targetClass.getDeclaredMethods()) + .filter(superMethod -> !superMethod.isBridge()) + .filter(superMethod -> hasSameSignature(sourceMethod, superMethod)) + .map(AnnotatedElement::getAnnotations) + .flatMap(Stream::of) + .toArray(Annotation[]::new); + } - /** - * 设置是否扫描类层级结构中具有相同方法签名的方法 - * - * @param scanSuperMethodIfOverride 是否扫描类层级结构中具有相同方法签名的方法 - * @return 当前实例 - */ - public MethodScanner setScanSameSignatureMethod(boolean scanSuperMethodIfOverride) { - setIncludeInterfaces(scanSuperMethodIfOverride); - setIncludeSupperClass(scanSuperMethodIfOverride); - return this; - } + /** + * 设置是否扫描类层级结构中具有相同方法签名的方法 + * + * @param scanSuperMethodIfOverride 是否扫描类层级结构中具有相同方法签名的方法 + * @return 当前实例 + */ + public MethodScanner setScanSameSignatureMethod(boolean scanSuperMethodIfOverride) { + setIncludeInterfaces(scanSuperMethodIfOverride); + setIncludeSupperClass(scanSuperMethodIfOverride); + return this; + } - /** - * 该方法是否具备与扫描的方法相同的方法签名 - */ - private boolean hasSameSignature(Method sourceMethod, Method superMethod) { - if (false == StringKit.equals(sourceMethod.getName(), superMethod.getName())) { - return false; - } - final Class[] sourceParameterTypes = sourceMethod.getParameterTypes(); - final Class[] targetParameterTypes = superMethod.getParameterTypes(); - if (sourceParameterTypes.length != targetParameterTypes.length) { - return false; - } - if (!ArrayKit.containsAll(sourceParameterTypes, targetParameterTypes)) { - return false; - } - return ClassKit.isAssignable(superMethod.getReturnType(), sourceMethod.getReturnType()); - } + /** + * 该方法是否具备与扫描的方法相同的方法签名 + */ + private boolean hasSameSignature(Method sourceMethod, Method superMethod) { + if (false == StringKit.equals(sourceMethod.getName(), superMethod.getName())) { + return false; + } + final Class[] sourceParameterTypes = sourceMethod.getParameterTypes(); + final Class[] targetParameterTypes = superMethod.getParameterTypes(); + if (sourceParameterTypes.length != targetParameterTypes.length) { + return false; + } + if (!ArrayKit.containsAll(sourceParameterTypes, targetParameterTypes)) { + return false; + } + return ClassKit.isAssignable(superMethod.getReturnType(), sourceMethod.getReturnType()); + } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/TypeScanner.java b/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/TypeScanner.java index 9a766dadfb..9c62a2e234 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/TypeScanner.java +++ b/bus-core/src/main/java/org/aoju/bus/core/scanner/annotation/TypeScanner.java @@ -37,95 +37,96 @@ /** * 扫描{@link Class}上的注解 + * * @author Kimi Liu * @since Java 17+ */ public class TypeScanner extends AbstractTypeScanner implements AnnotationScanner { - /** - * 构造一个类注解扫描器 - * - * @param includeSupperClass 是否允许扫描父类 - * @param includeInterfaces 是否允许扫描父接口 - * @param filter 过滤器 - * @param excludeTypes 不包含的类型 - */ - public TypeScanner(boolean includeSupperClass, boolean includeInterfaces, Predicate> filter, Set> excludeTypes) { - super(includeSupperClass, includeInterfaces, filter, excludeTypes); - } + /** + * 构造一个类注解扫描器 + * + * @param includeSupperClass 是否允许扫描父类 + * @param includeInterfaces 是否允许扫描父接口 + * @param filter 过滤器 + * @param excludeTypes 不包含的类型 + */ + public TypeScanner(boolean includeSupperClass, boolean includeInterfaces, Predicate> filter, Set> excludeTypes) { + super(includeSupperClass, includeInterfaces, filter, excludeTypes); + } - /** - * 构建一个类注解扫描器,默认允许扫描指定元素的父类以及父接口 - */ - public TypeScanner() { - this(true, true, t -> true, CollKit.newLinkedHashSet()); - } + /** + * 构建一个类注解扫描器,默认允许扫描指定元素的父类以及父接口 + */ + public TypeScanner() { + this(true, true, t -> true, CollKit.newLinkedHashSet()); + } - /** - * 判断是否支持扫描该注解元素,仅当注解元素是{@link Class}接时返回{@code true} - * - * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @return 是否支持扫描该注解元素 - */ - @Override - public boolean support(AnnotatedElement annotatedEle) { - return annotatedEle instanceof Class; - } + /** + * 判断是否支持扫描该注解元素,仅当注解元素是{@link Class}接时返回{@code true} + * + * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @return 是否支持扫描该注解元素 + */ + @Override + public boolean support(AnnotatedElement annotatedEle) { + return annotatedEle instanceof Class; + } - /** - * 将注解元素转为{@link Class} - * - * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission - * @return 要递归的类型 - */ - @Override - protected Class getClassFormAnnotatedElement(AnnotatedElement annotatedEle) { - return (Class) annotatedEle; - } + /** + * 将注解元素转为{@link Class} + * + * @param annotatedEle {@link AnnotatedElement},可以是Class、Method、Field、Constructor、ReflectPermission + * @return 要递归的类型 + */ + @Override + protected Class getClassFormAnnotatedElement(AnnotatedElement annotatedEle) { + return (Class) annotatedEle; + } - /** - * 获取{@link Class#getAnnotations()} - * - * @param source 最初的注解元素 - * @param index 类的层级索引 - * @param targetClass 类 - * @return 类上直接声明的注解 - */ - @Override - protected Annotation[] getAnnotationsFromTargetClass(AnnotatedElement source, int index, Class targetClass) { - return targetClass.getAnnotations(); - } + /** + * 获取{@link Class#getAnnotations()} + * + * @param source 最初的注解元素 + * @param index 类的层级索引 + * @param targetClass 类 + * @return 类上直接声明的注解 + */ + @Override + protected Annotation[] getAnnotationsFromTargetClass(AnnotatedElement source, int index, Class targetClass) { + return targetClass.getAnnotations(); + } - /** - * 是否允许扫描父类 - * - * @param includeSupperClass 是否允许扫描父类 - * @return 当前实例 - */ - @Override - public TypeScanner setIncludeSupperClass(boolean includeSupperClass) { - return super.setIncludeSupperClass(includeSupperClass); - } + /** + * 是否允许扫描父类 + * + * @param includeSupperClass 是否允许扫描父类 + * @return 当前实例 + */ + @Override + public TypeScanner setIncludeSupperClass(boolean includeSupperClass) { + return super.setIncludeSupperClass(includeSupperClass); + } - /** - * 是否允许扫描父接口 - * - * @param includeInterfaces 是否允许扫描父类 - * @return 当前实例 - */ - @Override - public TypeScanner setIncludeInterfaces(boolean includeInterfaces) { - return super.setIncludeInterfaces(includeInterfaces); - } + /** + * 是否允许扫描父接口 + * + * @param includeInterfaces 是否允许扫描父类 + * @return 当前实例 + */ + @Override + public TypeScanner setIncludeInterfaces(boolean includeInterfaces) { + return super.setIncludeInterfaces(includeInterfaces); + } - /** - * 若类型为jdk代理类,则尝试转换为原始被代理类 - */ - public static class JdkProxyClassConverter implements UnaryOperator> { - @Override - public Class apply(Class sourceClass) { - return Proxy.isProxyClass(sourceClass) ? apply(sourceClass.getSuperclass()) : sourceClass; - } - } + /** + * 若类型为jdk代理类,则尝试转换为原始被代理类 + */ + public static class JdkProxyClassConverter implements UnaryOperator> { + @Override + public Class apply(Class sourceClass) { + return Proxy.isProxyClass(sourceClass) ? apply(sourceClass.getSuperclass()) : sourceClass; + } + } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/text/Placeholder.java b/bus-core/src/main/java/org/aoju/bus/core/text/Placeholder.java new file mode 100644 index 0000000000..145f71b091 --- /dev/null +++ b/bus-core/src/main/java/org/aoju/bus/core/text/Placeholder.java @@ -0,0 +1,190 @@ +/********************************************************************************* + * * + * The MIT License (MIT) * + * * + * Copyright (c) 2015-2022 aoju.org and other contributors. * + * * + * Permission is hereby granted, free of charge, to any person obtaining a copy * + * of this software and associated documentation files (the "Software"), to deal * + * in the Software without restriction, including without limitation the rights * + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * + * copies of the Software, and to permit persons to whom the Software is * + * furnished to do so, subject to the following conditions: * + * * + * The above copyright notice and this permission notice shall be included in * + * all copies or substantial portions of the Software. * + * * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * + * THE SOFTWARE. * + * * + ********************************************************************************/ +package org.aoju.bus.core.text; + +import org.aoju.bus.core.exception.InternalException; +import org.aoju.bus.core.lang.Assert; +import org.aoju.bus.core.lang.Normal; +import org.aoju.bus.core.lang.Symbol; +import org.aoju.bus.core.toolkit.StringKit; + +import java.util.Objects; +import java.util.function.UnaryOperator; + +/** + * 简单的占位符解析器给定占位符的左右边界符号以及转义符, + * 将允许把一段字符串中的占位符解析并替换为指定内容,支持指定转义符对边界符号进行转义 + * 比如: + *
+ *   {@code
+ *     String text = "select * from #[tableName] where id = #[id]";
+ *     PlaceholderParser parser = new PlaceholderParser(str -> "?", "#[", "]");
+ *     parser.apply(text); // = select * from ? where id = ?
+ *   }
+ * 
+ * + * @author Kimi Liu + * @since Java 17+ + */ +public class Placeholder implements UnaryOperator { + + /** + * processor + */ + private final UnaryOperator processor; + + /** + * 占位符开始符号 + */ + private final String open; + + /** + * 结束符号长度 + */ + private final int openLength; + + /** + * 占位符结束符号 + */ + private final String close; + + /** + * 结束符号长度 + */ + private final int closeLength; + + /** + * 转义符 + */ + private final char escape; + + /** + * 创建一个占位符解析器,默认转义符为{@code "\"} + * + * @param processor 占位符处理器 + * @param prefix 占位符开始符号,不允许为空 + * @param suffix 占位符结束符号,不允许为空 + */ + public Placeholder( + final UnaryOperator processor, final String prefix, final String suffix) { + this(processor, prefix, suffix, Symbol.C_BACKSLASH); + } + + /** + * 创建一个占位符解析器 + * + * @param processor 占位符处理器 + * @param prefix 占位符开始符号,不允许为空 + * @param suffix 占位符结束符号,不允许为空 + * @param escape 转义符 + */ + public Placeholder( + final UnaryOperator processor, final String prefix, final String suffix, final char escape) { + Assert.isFalse(StringKit.isEmpty(prefix), "开始符号不能为空"); + Assert.isFalse(StringKit.isEmpty(suffix), "结束符号不能为空"); + this.processor = Objects.requireNonNull(processor); + this.open = prefix; + this.openLength = prefix.length(); + this.close = suffix; + this.closeLength = suffix.length(); + this.escape = escape; + } + + /** + * 解析并替换字符串中的占位符 + * + * @param text 待解析的字符串 + * @return 处理后的字符串 + */ + @Override + public String apply(final String text) { + if (StringKit.isEmpty(text)) { + return Normal.EMPTY; + } + + // 寻找第一个开始符号 + int closeCursor = 0; + int openCursor = text.indexOf(open, closeCursor); + if (openCursor == -1) { + return text; + } + + // 开始匹配 + final char[] src = text.toCharArray(); + final StringBuilder result = new StringBuilder(src.length); + final StringBuilder expression = new StringBuilder(); + while (openCursor > -1) { + + // 开始符号是否被转义,若是则跳过并寻找下一个开始符号 + if (openCursor > 0 && src[openCursor - 1] == escape) { + result.append(src, closeCursor, openCursor - closeCursor - 1).append(open); + closeCursor = openCursor + openLength; + openCursor = text.indexOf(open, closeCursor); + continue; + } + + // 记录当前位符的开始符号与上一占位符的结束符号间的字符串 + result.append(src, closeCursor, openCursor - closeCursor); + // 重置结束游标至当前占位符的开始处 + closeCursor = openCursor + openLength; + // 寻找结束符号下标 + int end = text.indexOf(close, closeCursor); + while (end > -1) { + // 结束符号被转义,寻找下一个结束符号 + if (end > closeCursor && src[end - 1] == escape) { + expression.append(src, closeCursor, end - closeCursor - 1).append(close); + closeCursor = end + closeLength; + end = text.indexOf(close, closeCursor); + } + // 找到结束符号 + else { + expression.append(src, closeCursor, end - closeCursor); + break; + } + } + + // 未能找到结束符号,说明匹配异常 + if (end == -1) { + throw new InternalException("\"{}\" 中字符下标 {} 处的开始符没有找到对应的结束符", text, openCursor); + } + // 找到结束符号,将开始到结束符号之间的字符串替换为指定表达式 + else { + result.append(processor.apply(expression.toString())); + expression.setLength(0); + // 完成当前占位符的处理匹配,寻找下一个 + closeCursor = end + close.length(); + } + // 寻找下一个开始符号 + openCursor = text.indexOf(open, closeCursor); + } + // 若匹配结束后仍有未处理的字符串,则直接将其拼接到表达式上 + if (closeCursor < src.length) { + result.append(src, closeCursor, src.length - closeCursor); + } + return result.toString(); + } + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/text/TextBuilder.java b/bus-core/src/main/java/org/aoju/bus/core/text/TextBuilder.java index 0288e9472b..3bb20412be 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/text/TextBuilder.java +++ b/bus-core/src/main/java/org/aoju/bus/core/text/TextBuilder.java @@ -118,7 +118,7 @@ public TextBuilder(CharSequence... texts) { * * @return {@link TextBuilder} */ - public static TextBuilder create() { + public static TextBuilder of() { return new TextBuilder(); } @@ -1219,7 +1219,7 @@ public TextBuilder appendFixedWidthPadLeft(final int value, final int width, fin * 如果物体比长度大,右边的部分就会丢失 * 如果对象为空,则使用空文本值 * - * @param object 要追加的对象null使用空文本 + * @param object 要追加的对象null使用空文本 * @param width 固定的字段宽度,零或负没有影响 * @param padChar 要使用的填充字符 * @return this @@ -1264,8 +1264,8 @@ public TextBuilder appendFixedWidthPadRight(final int value, final int width, fi * 将对象的字符串表示形式插入到此生成器中 * 插入null将使用存储的空文本值 * - * @param index 要添加的索引必须有效 - * @param object 要插入的对象 + * @param index 要添加的索引必须有效 + * @param object 要插入的对象 * @return this * @throws IndexOutOfBoundsException 如果索引无效 */ diff --git a/bus-core/src/main/java/org/aoju/bus/core/text/TextJoiner.java b/bus-core/src/main/java/org/aoju/bus/core/text/TextJoiner.java index f5cb04fa7f..ae59526855 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/text/TextJoiner.java +++ b/bus-core/src/main/java/org/aoju/bus/core/text/TextJoiner.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.io.Serializable; import java.util.Iterator; +import java.util.Map; import java.util.function.Function; /** @@ -211,6 +212,13 @@ public TextJoiner setEmptyResult(String text) { /** * 追加对象到拼接器中 + *
    + *
  • null,按照 {@link #nullMode} 策略追加
  • + *
  • array,逐个追加
  • + *
  • {@link Iterator},逐个追加
  • + *
  • {@link Iterable},逐个追加
  • + *
  • {@link Map.Entry},追加键,分隔符,再追加值
  • + *
* * @param object 对象,支持数组、集合等 * @return this @@ -224,6 +232,9 @@ public TextJoiner append(Object object) { append((Iterator) object); } else if (object instanceof Iterable) { append(((Iterable) object).iterator()); + } else if (object instanceof Map.Entry) { + final Map.Entry entry = (Map.Entry) object; + append(entry.getKey()).append(entry.getValue()); } else { append(ObjectKit.toString(object)); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/HfFilter.java b/bus-core/src/main/java/org/aoju/bus/core/text/bloom/AbstractFilter.java similarity index 72% rename from bus-core/src/main/java/org/aoju/bus/core/bloom/filter/HfFilter.java rename to bus-core/src/main/java/org/aoju/bus/core/text/bloom/AbstractFilter.java index 7ad3ceda86..b56a789f0d 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/HfFilter.java +++ b/bus-core/src/main/java/org/aoju/bus/core/text/bloom/AbstractFilter.java @@ -23,38 +23,55 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.core.bloom.filter; +package org.aoju.bus.core.text.bloom; + +import java.util.BitSet; /** + * 抽象Bloom过滤器 + * * @author Kimi Liu * @since Java 17+ */ -public class HfFilter extends AbstractFilter { +public abstract class AbstractFilter implements BloomFilter { private static final long serialVersionUID = 1L; - public HfFilter(long maxValue, int machineNum) { - super(maxValue, machineNum); - } + private final BitSet bitSet; + protected int size; - public HfFilter(long maxValue) { - super(maxValue); + /** + * 构造 + * + * @param size 容量 + */ + public AbstractFilter(final int size) { + this.size = size; + this.bitSet = new BitSet(size); } @Override - public long hash(String text) { - int length = text.length(); - long hash = 0; - - for (int i = 0; i < length; i++) { - hash += text.charAt(i) * 3 * i; - } + public boolean contains(final String text) { + return bitSet.get(Math.abs(hash(text))); + } - if (hash < 0) { - hash = -hash; + @Override + public boolean add(final String text) { + final int hash = Math.abs(hash(text)); + if (bitSet.get(hash)) { + return false; } - return hash % size; + bitSet.set(hash); + return true; } + /** + * 自定义Hash方法 + * + * @param text 字符串 + * @return HashCode + */ + public abstract int hash(String text); + } diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/BloomFilter.java b/bus-core/src/main/java/org/aoju/bus/core/text/bloom/BloomFilter.java similarity index 84% rename from bus-core/src/main/java/org/aoju/bus/core/bloom/BloomFilter.java rename to bus-core/src/main/java/org/aoju/bus/core/text/bloom/BloomFilter.java index 17eebc8e7d..40372f918e 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/BloomFilter.java +++ b/bus-core/src/main/java/org/aoju/bus/core/text/bloom/BloomFilter.java @@ -23,14 +23,14 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.core.bloom; +package org.aoju.bus.core.text.bloom; import java.io.Serializable; /** * Bloom filter 是由 Howard Bloom 在 1970 年提出的二进制向量数据结构,它具有很好的空间和时间效率,被用来检测一个元素是不是集合中的一个成员 - * 如果检测结果为是,该元素不一定在集合中;但如果检测结果为否,该元素一定不在集合中,因此Bloom filter具有100%的召回率 - * 这样每个检测请求返回有“在集合内(可能错误)”和“不在集合内(绝对不在集合内)”两种情况 + * 如果检测结果为是,该元素不一定在集合中;但如果检测结果为否,该元素一定不在集合中 + * 因此Bloom filter具有100%的召回率这样每个检测请求返回有“在集合内(可能错误)”和“不在集合内(绝对不在集合内)”两种情况 * * @author Kimi Liu * @since Java 17+ @@ -45,10 +45,11 @@ public interface BloomFilter extends Serializable { /** * 在boolean的bitMap中增加一个字符串 - * 如果存在就返回false如果不存在先增加这个字符串.再返回true + * 如果存在就返回{@code false} .如果不存在.先增加这个字符串.再返回{@code true} * * @param text 字符串 - * @return 是否加入成功,如果存在就返回false如果不存在返回true + * @return 是否加入成功,如果存在就返回{@code false} .如果不存在返回{@code true} */ boolean add(String text); -} \ No newline at end of file + +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/BitMapBloomFilter.java b/bus-core/src/main/java/org/aoju/bus/core/text/bloom/CombinedFilter.java similarity index 72% rename from bus-core/src/main/java/org/aoju/bus/core/bloom/BitMapBloomFilter.java rename to bus-core/src/main/java/org/aoju/bus/core/text/bloom/CombinedFilter.java index c0fd66671f..4c4f5c466f 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/BitMapBloomFilter.java +++ b/bus-core/src/main/java/org/aoju/bus/core/text/bloom/CombinedFilter.java @@ -23,14 +23,10 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.core.bloom; - -import org.aoju.bus.core.bloom.filter.*; -import org.aoju.bus.core.lang.Normal; -import org.aoju.bus.core.toolkit.MathKit; +package org.aoju.bus.core.text.bloom; /** - * BlommFilter 实现 + * 组合BloomFilter 实现 * 1.构建hash算法 * 2.散列hash映射到数组的bit位置 * 3.验证 @@ -39,38 +35,18 @@ * @author Kimi Liu * @since Java 17+ */ -public class BitMapBloomFilter implements BloomFilter { +public class CombinedFilter implements BloomFilter { private static final long serialVersionUID = 1L; - private BloomFilter[] filters; - - /** - * 构造,使用默认的5个过滤器 - * - * @param m M值决定BitMap的大小 - */ - public BitMapBloomFilter(int m) { - long mNum = MathKit.div(String.valueOf(m), String.valueOf(5)).longValue(); - long size = mNum * Normal._1024 * Normal._1024 * 8; - - filters = new BloomFilter[]{ - new DefaultFilter(size), - new ELFFilter(size), - new JSFilter(size), - new PJWFilter(size), - new SDBMFilter(size) - }; - } + private final BloomFilter[] filters; /** * 使用自定的多个过滤器建立BloomFilter * - * @param m M值决定BitMap的大小 * @param filters Bloom过滤器列表 */ - public BitMapBloomFilter(int m, BloomFilter... filters) { - this(m); + public CombinedFilter(final BloomFilter... filters) { this.filters = filters; } @@ -80,9 +56,9 @@ public BitMapBloomFilter(int m, BloomFilter... filters) { * @param text 字符串 */ @Override - public boolean add(String text) { + public boolean add(final String text) { boolean flag = false; - for (BloomFilter filter : filters) { + for (final BloomFilter filter : filters) { flag |= filter.add(text); } return flag; @@ -95,8 +71,8 @@ public boolean add(String text) { * @return 是否存在 */ @Override - public boolean contains(String text) { - for (BloomFilter filter : filters) { + public boolean contains(final String text) { + for (final BloomFilter filter : filters) { if (filter.contains(text) == false) { return false; } @@ -104,4 +80,4 @@ public boolean contains(String text) { return true; } -} \ No newline at end of file +} diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/JSFilter.java b/bus-core/src/main/java/org/aoju/bus/core/text/bloom/FuncFilter.java similarity index 72% rename from bus-core/src/main/java/org/aoju/bus/core/bloom/filter/JSFilter.java rename to bus-core/src/main/java/org/aoju/bus/core/text/bloom/FuncFilter.java index f0ad74ee9d..eb5097a8a6 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/filter/JSFilter.java +++ b/bus-core/src/main/java/org/aoju/bus/core/text/bloom/FuncFilter.java @@ -23,37 +23,45 @@ * THE SOFTWARE. * * * ********************************************************************************/ -package org.aoju.bus.core.bloom.filter; +package org.aoju.bus.core.text.bloom; + +import java.util.function.Function; /** + * 基于Hash函数方法的{@link BloomFilter} + * * @author Kimi Liu * @since Java 17+ */ -public class JSFilter extends AbstractFilter { +public class FuncFilter extends AbstractFilter { private static final long serialVersionUID = 1L; - public JSFilter(long maxValue, int machineNum) { - super(maxValue, machineNum); + private final Function hashFunc; + + /** + * @param size 最大值 + * @param hashFunc Hash函数 + */ + public FuncFilter(final int size, final Function hashFunc) { + super(size); + this.hashFunc = hashFunc; } - public JSFilter(long maxValue) { - super(maxValue); + /** + * 创建FuncFilter + * + * @param size 最大值 + * @param hashFunc Hash函数 + * @return FuncFilter + */ + public static FuncFilter of(final int size, final Function hashFunc) { + return new FuncFilter(size, hashFunc); } @Override - public long hash(String text) { - int hash = 1315423911; - - for (int i = 0; i < text.length(); i++) { - hash ^= ((hash << 5) + text.charAt(i) + (hash >> 2)); - } - - if (hash < 0) { - hash *= -1; - } - - return hash % size; + public int hash(final String text) { + return hashFunc.apply(text).intValue() % size; } } diff --git a/bus-core/src/main/java/org/aoju/bus/core/bloom/package-info.java b/bus-core/src/main/java/org/aoju/bus/core/text/bloom/package-info.java similarity index 73% rename from bus-core/src/main/java/org/aoju/bus/core/bloom/package-info.java rename to bus-core/src/main/java/org/aoju/bus/core/text/bloom/package-info.java index d21a31c0e7..27dc9df885 100644 --- a/bus-core/src/main/java/org/aoju/bus/core/bloom/package-info.java +++ b/bus-core/src/main/java/org/aoju/bus/core/text/bloom/package-info.java @@ -4,4 +4,4 @@ * @author Kimi Liu * @since Java 17+ */ -package org.aoju.bus.core.bloom; \ No newline at end of file +package org.aoju.bus.core.text.bloom; diff --git a/bus-core/src/main/java/org/aoju/bus/core/thread/ExecutorBuilder.java b/bus-core/src/main/java/org/aoju/bus/core/thread/ExecutorBuilder.java index 9dec3708cf..2fee48925a 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/thread/ExecutorBuilder.java +++ b/bus-core/src/main/java/org/aoju/bus/core/thread/ExecutorBuilder.java @@ -84,7 +84,7 @@ public class ExecutorBuilder implements Builder { * * @return {@link ExecutorBuilder} */ - public static ExecutorBuilder create() { + public static ExecutorBuilder of() { return new ExecutorBuilder(); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/thread/GlobalThread.java b/bus-core/src/main/java/org/aoju/bus/core/thread/GlobalThread.java index d248b9dc26..bb11b59f60 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/thread/GlobalThread.java +++ b/bus-core/src/main/java/org/aoju/bus/core/thread/GlobalThread.java @@ -55,7 +55,7 @@ synchronized public static void init() { if (null != executor) { executor.shutdownNow(); } - executor = ExecutorBuilder.create().useSynchronousQueue().build(); + executor = ExecutorBuilder.of().useSynchronousQueue().build(); } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/thread/ThreadBuilder.java b/bus-core/src/main/java/org/aoju/bus/core/thread/ThreadBuilder.java index a526f1660b..88f9c815bc 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/thread/ThreadBuilder.java +++ b/bus-core/src/main/java/org/aoju/bus/core/thread/ThreadBuilder.java @@ -67,7 +67,7 @@ public class ThreadBuilder implements Builder { * * @return {@link ThreadBuilder} */ - public static ThreadBuilder create() { + public static ThreadBuilder of() { return new ThreadBuilder(); } diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/BeanKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/BeanKit.java index 15a5d86ca8..b1127c926e 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/BeanKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/BeanKit.java @@ -93,16 +93,16 @@ public static boolean isReadable(Class clazz) { * 判断Bean是否为空对象,空对象表示本身为null或者所有属性都为null * * @param bean Bean对象 - * @param ignoreFiledNames 忽略检查的字段名 + * @param ignoreFieldNames 忽略检查的字段名 * @return 是否为空,true - 空 / false - 非空 */ - public static boolean isEmpty(Object bean, String... ignoreFiledNames) { + public static boolean isEmpty(Object bean, String... ignoreFieldNames) { if (null != bean) { for (Field field : ReflectKit.getFields(bean.getClass())) { if (isStatic(field)) { continue; } - if ((false == ArrayKit.contains(ignoreFiledNames, field.getName())) + if ((false == ArrayKit.contains(ignoreFieldNames, field.getName())) && null != ReflectKit.getFieldValue(bean, field)) { return false; } @@ -115,11 +115,11 @@ public static boolean isEmpty(Object bean, String... ignoreFiledNames) { * 判断Bean是否为非空对象,非空对象表示本身不为null或者含有非null属性的对象 * * @param bean Bean对象 - * @param ignoreFiledNames 忽略检查的字段名 + * @param ignoreFieldNames 忽略检查的字段名 * @return 是否为空,true - 空 / false - 非空 */ - public static boolean isNotEmpty(Object bean, String... ignoreFiledNames) { - return false == isEmpty(bean, ignoreFiledNames); + public static boolean isNotEmpty(Object bean, String... ignoreFieldNames) { + return false == isEmpty(bean, ignoreFieldNames); } /** @@ -227,10 +227,10 @@ public static boolean hasNullField(Object bean) { * 对象本身为null也返回true * * @param bean Bean对象 - * @param ignoreFiledNames 忽略检查的字段名 + * @param ignoreFieldNames 忽略检查的字段名 * @return 是否包含值为null的属性,true - 包含 / false - 不包含 */ - public static boolean hasNullField(Object bean, String... ignoreFiledNames) { + public static boolean hasNullField(Object bean, String... ignoreFieldNames) { if (null == bean) { return true; } @@ -238,7 +238,7 @@ public static boolean hasNullField(Object bean, String... ignoreFiledNames) { if (isStatic(field)) { continue; } - if ((false == ArrayKit.contains(ignoreFiledNames, field.getName())) + if ((false == ArrayKit.contains(ignoreFieldNames, field.getName())) && null == ReflectKit.getFieldValue(bean, field)) { return true; } @@ -461,10 +461,10 @@ public static void setFieldValue(Object bean, String fieldNameOrIndex, Object va * @param bean Bean对象,支持Map、List、Collection、Array * @param expression 表达式,例如:person.friend[5].name * @return Bean属性值 - * @see PathExpression#get(Object) + * @see BeanPath#get(Object) */ public static T getProperty(Object bean, String expression) { - return (T) PathExpression.create(expression).get(bean); + return (T) BeanPath.of(expression).get(bean); } /** @@ -473,10 +473,10 @@ public static T getProperty(Object bean, String expression) { * @param bean Bean对象,支持Map、List、Collection、Array * @param expression 表达式,例如:person.friend[5].name * @param value 值 - * @see PathExpression#get(Object) + * @see BeanPath#get(Object) */ public static void setProperty(Object bean, String expression, Object value) { - PathExpression.create(expression).set(bean, value); + BeanPath.of(expression).set(bean, value); } /** diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/BloomKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/BloomKit.java deleted file mode 100644 index 5a289826bf..0000000000 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/BloomKit.java +++ /dev/null @@ -1,61 +0,0 @@ -/********************************************************************************* - * * - * The MIT License (MIT) * - * * - * Copyright (c) 2015-2022 aoju.org and other contributors. * - * * - * Permission is hereby granted, free of charge, to any person obtaining a copy * - * of this software and associated documentation files (the "Software"), to deal * - * in the Software without restriction, including without limitation the rights * - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * - * copies of the Software, and to permit persons to whom the Software is * - * furnished to do so, subject to the following conditions: * - * * - * The above copyright notice and this permission notice shall be included in * - * all copies or substantial portions of the Software. * - * * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * - * THE SOFTWARE. * - * * - ********************************************************************************/ -package org.aoju.bus.core.toolkit; - -import org.aoju.bus.core.bloom.BitMapBloomFilter; -import org.aoju.bus.core.bloom.BitSetBloomFilter; - -/** - * 布隆过滤器工具 - * - * @author Kimi Liu - * @since Java 17+ - */ -public class BloomKit { - - /** - * 创建一个BitSet实现的布隆过滤器,过滤器的容量为c * n 个bit - * - * @param c 当前过滤器预先开辟的最大包含记录,通常要比预计存入的记录多一倍 - * @param n 当前过滤器预计所要包含的记录 - * @param k 哈希函数的个数,等同每条记录要占用的bit数 - * @return the object - */ - public static BitSetBloomFilter createBitSet(int c, int n, int k) { - return new BitSetBloomFilter(c, n, k); - } - - /** - * 创建BitMap实现的布隆过滤器 - * - * @param m BitMap的大小 - * @return the object - */ - public static BitMapBloomFilter createBitMap(int m) { - return new BitMapBloomFilter(m); - } - -} diff --git a/bus-core/src/main/java/org/aoju/bus/core/toolkit/CharsKit.java b/bus-core/src/main/java/org/aoju/bus/core/toolkit/CharsKit.java index ae0ee0faa9..bce19a2328 100755 --- a/bus-core/src/main/java/org/aoju/bus/core/toolkit/CharsKit.java +++ b/bus-core/src/main/java/org/aoju/bus/core/toolkit/CharsKit.java @@ -293,10 +293,10 @@ public static boolean isBlankChar(final int args) { * * 例: *
    - *
  • {@code StringKit.isBlank(null) // true}
  • - *
  • {@code StringKit.isBlank("") // true}
  • - *
  • {@code StringKit.isBlank(" \t\n") // true}
  • - *
  • {@code StringKit.isBlank("abc") // false}
  • + *
  • {@code isBlank(null) // true}
  • + *
  • {@code isBlank("") // true}
  • + *
  • {@code isBlank(" \t\n") // true}
  • + *
  • {@code isBlank("abc") // false}
  • *
* 注意:该方法与 {@link #isEmpty(CharSequence)} 的区别是: * 该方法会校验空白字符,且性能相对于 {@link #isEmpty(CharSequence)} 略慢 @@ -336,10 +336,10 @@ public static boolean isBlank(CharSequence text) { * * 例: *
    - *
  • {@code StringKit.isNotBlank(null) // false}
  • - *
  • {@code StringKit.isNotBlank("") // false}
  • - *
  • {@code StringKit.isNotBlank(" \t\n") // false}
  • - *
  • {@code StringKit.isNotBlank("abc") // true}
  • + *
  • {@code isNotBlank(null) // false}
  • + *
  • {@code isNotBlank("") // false}
  • + *
  • {@code isNotBlank(" \t\n") // false}
  • + *
  • {@code isNotBlank("abc") // true}
  • *
* 注意:该方法与 {@link #isNotEmpty(CharSequence)} 的区别是: * 该方法会校验空白字符,且性能相对于 {@link #isNotEmpty(CharSequence)} 略慢 @@ -357,16 +357,16 @@ public static boolean isNotBlank(CharSequence text) { * 检查是否没有字符序列为空("")、空字符或仅为空格 * *
-     * StringKit.isNoneBlank(null)             = false
-     * StringKit.isNoneBlank(null, "foo")      = false
-     * StringKit.isNoneBlank(null, null)       = false
-     * StringKit.isNoneBlank("", "bar")        = false
-     * StringKit.isNoneBlank("bob", "")        = false
-     * StringKit.isNoneBlank("  bob  ", null)  = false
-     * StringKit.isNoneBlank(" ", "bar")       = false
-     * StringKit.isNoneBlank(new String[] {})  = true
-     * StringKit.isNoneBlank(new String[]{""}) = false
-     * StringKit.isNoneBlank("foo", "bar")     = true
+     * isNoneBlank(null)             = false
+     * isNoneBlank(null, "foo")      = false
+     * isNoneBlank(null, null)       = false
+     * isNoneBlank("", "bar")        = false
+     * isNoneBlank("bob", "")        = false
+     * isNoneBlank("  bob  ", null)  = false
+     * isNoneBlank(" ", "bar")       = false
+     * isNoneBlank(new String[] {})  = true
+     * isNoneBlank(new String[]{""}) = false
+     * isNoneBlank("foo", "bar")     = true
      * 
* * @param args 要检查的字符串可以为null或空 @@ -380,17 +380,17 @@ public static boolean isNoneBlank(final CharSequence... args) { * 检查任何一个字符序列是否为空(""),或为空,或仅为空白 * *
-     * StringKit.isAnyBlank((String) null)    = true
-     * StringKit.isAnyBlank((String[]) null)  = false
-     * StringKit.isAnyBlank(null, "foo")      = true
-     * StringKit.isAnyBlank(null, null)       = true
-     * StringKit.isAnyBlank("", "bar")        = true
-     * StringKit.isAnyBlank("bob", "")        = true
-     * StringKit.isAnyBlank("  bob  ", null)  = true
-     * StringKit.isAnyBlank(" ", "bar")       = true
-     * StringKit.isAnyBlank(new String[] {})  = false
-     * StringKit.isAnyBlank(new String[]{""}) = true
-     * StringKit.isAnyBlank("foo", "bar")     = false
+     * isAnyBlank((String) null)    = true
+     * isAnyBlank((String[]) null)  = false
+     * isAnyBlank(null, "foo")      = true
+     * isAnyBlank(null, null)       = true
+     * isAnyBlank("", "bar")        = true
+     * isAnyBlank("bob", "")        = true
+     * isAnyBlank("  bob  ", null)  = true
+     * isAnyBlank(" ", "bar")       = true
+     * isAnyBlank(new String[] {})  = false
+     * isAnyBlank(new String[]{""}) = true
+     * isAnyBlank("foo", "bar")     = false
      * 
* * @param args 要检查的字符序列可以为空或空 @@ -413,10 +413,10 @@ public static boolean isAnyBlank(final CharSequence... args) { * 如果指定的字符串数组的长度为 0,或者所有元素都是空字符串,则返回 true * 例: *
    - *
  • {@code StringKit.isAllBlank() // true}
  • - *
  • {@code StringKit.isAllBlank("", null, " ") // true}
  • - *
  • {@code StringKit.isAllBlank("123", " ") // false}
  • - *
  • {@code StringKit.isAllBlank("123", "abc") // false}
  • + *
  • {@code isAllBlank() // true}
  • + *
  • {@code isAllBlank("", null, " ") // true}
  • + *
  • {@code isAllBlank("123", " ") // false}
  • + *
  • {@code isAllBlank("123", "abc") // false}
  • *
* 注意:该方法与 {@link #hasBlank(CharSequence...)} 的区别在于: *