@@ -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 extends V> 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 extends Number
throw new UnsupportedOperationException(StringKit.format("Unsupport Number type: {}", targetType.getName()));
}
- @Override
- protected Number convertInternal(final Class> targetClass, final Object value) {
- return convert(value, (Class extends Number>) 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
- *
- * @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.
*/
- 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,随着数据的增长自动扩充缓冲区
+ *
+ *
+ * @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异常,包括
+ *
+ *
FileNotFoundException:服务端无返回内容
+ *
EOFException: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.