mirror of
https://gitee.com/chinabugotech/hutool.git
synced 2025-05-09 23:51:34 +08:00
Merge remote-tracking branch 'origin/v5-dev' into v5-dev
This commit is contained in:
commit
97cc945a67
15
CHANGELOG.md
15
CHANGELOG.md
@ -2,14 +2,20 @@
|
||||
# 🚀Changelog
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
# 5.8.0 (2022-03-20)
|
||||
# 5.8.0 (2022-03-22)
|
||||
|
||||
### ❌不兼容特性
|
||||
* 【db 】 【不向下兼容】增加MongoDB4.x支持(pr#568@Gitee)
|
||||
* 【db 】 【不向下兼容 】增加MongoDB4.x支持返回MongoClient变更(pr#568@Gitee)
|
||||
* 【json 】 【可能兼容问题】修改JSONObject结构,继承自MapWrapper
|
||||
* 【core 】 【可能兼容问题】BeanCopier重构,新建XXXCopier,删除XXXValueProvider
|
||||
* 【core 】 【可能兼容问题】URLEncoder废弃,URLEncoderUtil使用RFC3986
|
||||
* 【core 】 【可能兼容问题】增加Base32.encode不足位数补=
|
||||
* 【core 】 【可能兼容问题】Base32分离编码和解码,以便减少数据加载,支持Hex模式
|
||||
* 【core 】 【可能兼容问题】Base58分离编码和解码
|
||||
* 【core 】 【可能兼容问题】Base62分离编码和解码,增加inverted模式支持
|
||||
* 【core 】 【兼容问题 】PunyCode参数由String改为Charsequence
|
||||
* 【cron 】 【可能兼容问题】SimpleValueParser改名为AbsValueParser,改为abstract
|
||||
* 【poi 】 【可能兼容问题】ExcelUtil.getBigWriter返回值改为BigExcelWriter
|
||||
* 【core 】 【可能兼容问题】Opt.ofEmptyAble参数由List改为Collection子类(pr#580@Gitee)
|
||||
|
||||
### 🐣新特性
|
||||
* 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee)
|
||||
@ -30,6 +36,8 @@
|
||||
* 【poi 】 优化ExcelBase,将alias放入
|
||||
* 【poi 】 优化ExcelBase,将alias放入
|
||||
* 【core 】 改进StrUtil#startWith、endWith性能
|
||||
* 【cron 】 增加CronPatternParser、MatcherTable
|
||||
* 【http 】 GlobalHeaders增加系统属性allowUnsafeServerCertChange、allowUnsafeRenegotiation
|
||||
|
||||
### 🐞Bug修复
|
||||
* 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee)
|
||||
@ -41,6 +49,7 @@
|
||||
* 【core 】 修复CopyOptions中fieldNameEditor无效问题(issue#2202@Github)
|
||||
* 【json 】 修复JSON对Map.Entry的解析问题
|
||||
* 【core 】 修复MapConverter中map与map转换兼容问题
|
||||
* 【poi 】 解决sax读取时,POI-5.2.x兼容性问题
|
||||
|
||||
-------------------------------------------------------------------------------------------------------------
|
||||
# 5.7.22 (2022-03-01)
|
||||
|
@ -6,85 +6,28 @@ import cn.hutool.core.util.StrUtil;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* Base32 - encodes and decodes RFC3548 Base32 (see http://www.faqs.org/rfcs/rfc3548.html )<br>
|
||||
* Base32 - encodes and decodes RFC4648 Base32 (see https://datatracker.ietf.org/doc/html/rfc4648#section-6 )<br>
|
||||
* base32就是用32(2的5次方)个特定ASCII码来表示256个ASCII码。<br>
|
||||
* 所以,5个ASCII字符经过base32编码后会变为8个字符(公约数为40),长度增加3/5.不足8n用“=”补足。
|
||||
* see http://blog.csdn.net/earbao/article/details/44453937
|
||||
* @author Looly
|
||||
* 所以,5个ASCII字符经过base32编码后会变为8个字符(公约数为40),长度增加3/5.不足8n用“=”补足。<br>
|
||||
* 根据RFC4648 Base32规范,支持两种模式:
|
||||
* <ul>
|
||||
* <li>Base 32 Alphabet (ABCDEFGHIJKLMNOPQRSTUVWXYZ234567)</li>
|
||||
* <li>"Extended Hex" Base 32 Alphabet (0123456789ABCDEFGHIJKLMNOPQRSTUV)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class Base32 {
|
||||
|
||||
private Base32() {}
|
||||
|
||||
private static final String BASE32_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
private static final int[] BASE32_LOOKUP = {//
|
||||
0xFF, 0xFF, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, // '0', '1', '2', '3', '4', '5', '6', '7'
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // '8', '9', ':', ';', '<', '=', '>', '?'
|
||||
0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, // '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G'
|
||||
0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, // 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O'
|
||||
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, // 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W'
|
||||
0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // 'X', 'Y', 'Z', '[', '\', ']', '^', '_'
|
||||
0xFF, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, // '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g'
|
||||
0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, // 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o'
|
||||
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, // 'p', 'q', 'r', 's', 't', 'u', 'v', 'w'
|
||||
0x17, 0x18, 0x19, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // 'x', 'y', 'z', '{', '|', '}', '~', 'DEL'
|
||||
};
|
||||
private static final int[] BASE32_FILL = {-1, 4, 1, 6, 3};
|
||||
|
||||
//----------------------------------------------------------------------------------------- encode
|
||||
|
||||
/**
|
||||
* 编码
|
||||
*
|
||||
* @param bytes 数据
|
||||
* @return base32
|
||||
*/
|
||||
public static String encode(final byte[] bytes) {
|
||||
int i = 0;
|
||||
int index = 0;
|
||||
int digit;
|
||||
int currByte;
|
||||
int nextByte;
|
||||
|
||||
int encodeLen = bytes.length * 8 / 5;
|
||||
if (encodeLen != 0) {
|
||||
encodeLen = encodeLen + 1 + BASE32_FILL[(bytes.length * 8) % 5];
|
||||
}
|
||||
|
||||
StringBuilder base32 = new StringBuilder(encodeLen);
|
||||
|
||||
while (i < bytes.length) {
|
||||
// unsign
|
||||
currByte = (bytes[i] >= 0) ? bytes[i] : (bytes[i] + 256);
|
||||
|
||||
/* Is the current digit going to span a byte boundary? */
|
||||
if (index > 3) {
|
||||
if ((i + 1) < bytes.length) {
|
||||
nextByte = (bytes[i + 1] >= 0) ? bytes[i + 1] : (bytes[i + 1] + 256);
|
||||
} else {
|
||||
nextByte = 0;
|
||||
}
|
||||
|
||||
digit = currByte & (0xFF >> index);
|
||||
index = (index + 5) % 8;
|
||||
digit <<= index;
|
||||
digit |= nextByte >> (8 - index);
|
||||
i++;
|
||||
} else {
|
||||
digit = (currByte >> (8 - (index + 5))) & 0x1F;
|
||||
index = (index + 5) % 8;
|
||||
if (index == 0) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
base32.append(BASE32_CHARS.charAt(digit));
|
||||
}
|
||||
|
||||
// 末尾补充不足长度的
|
||||
while(base32.length() < encodeLen){
|
||||
base32.append('=');
|
||||
}
|
||||
|
||||
return base32.toString();
|
||||
return Base32Codec.INSTANCE.encode(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -100,18 +43,7 @@ public class Base32 {
|
||||
/**
|
||||
* base32编码
|
||||
*
|
||||
* @param source 被编码的base32字符串
|
||||
* @param charset 字符集
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String encode(String source, String charset) {
|
||||
return encode(StrUtil.bytes(source, charset));
|
||||
}
|
||||
|
||||
/**
|
||||
* base32编码
|
||||
*
|
||||
* @param source 被编码的base32字符串
|
||||
* @param source 被编码的base32字符串
|
||||
* @param charset 字符集
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
@ -119,55 +51,47 @@ public class Base32 {
|
||||
return encode(StrUtil.bytes(source, charset));
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码
|
||||
*
|
||||
* @param bytes 数据(Hex模式)
|
||||
* @return base32
|
||||
*/
|
||||
public static String encodeHex(final byte[] bytes) {
|
||||
return Base32Codec.INSTANCE.encode(bytes, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* base32编码(Hex模式)
|
||||
*
|
||||
* @param source 被编码的base32字符串
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String encodeHex(String source) {
|
||||
return encodeHex(source, CharsetUtil.CHARSET_UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* base32编码(Hex模式)
|
||||
*
|
||||
* @param source 被编码的base32字符串
|
||||
* @param charset 字符集
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String encodeHex(String source, Charset charset) {
|
||||
return encodeHex(StrUtil.bytes(source, charset));
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------------------------- decode
|
||||
|
||||
/**
|
||||
* 解码
|
||||
*
|
||||
* @param base32 base32编码
|
||||
* @return 数据
|
||||
*/
|
||||
public static byte[] decode(final String base32) {
|
||||
int i, index, lookup, offset, digit;
|
||||
int len = base32.endsWith("=") ? base32.indexOf("=") * 5 / 8 : base32.length() * 5 / 8;
|
||||
byte[] bytes = new byte[len];
|
||||
|
||||
for (i = 0, index = 0, offset = 0; i < base32.length(); i++) {
|
||||
lookup = base32.charAt(i) - '0';
|
||||
|
||||
/* Skip chars outside the lookup table */
|
||||
if (lookup < 0 || lookup >= BASE32_LOOKUP.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
digit = BASE32_LOOKUP[lookup];
|
||||
|
||||
/* If this digit is not in the table, ignore it */
|
||||
if (digit == 0xFF) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (index <= 3) {
|
||||
index = (index + 5) % 8;
|
||||
if (index == 0) {
|
||||
bytes[offset] |= digit;
|
||||
offset++;
|
||||
if (offset >= bytes.length) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
bytes[offset] |= digit << (8 - index);
|
||||
}
|
||||
} else {
|
||||
index = (index + 5) % 8;
|
||||
bytes[offset] |= (digit >>> index);
|
||||
offset++;
|
||||
|
||||
if (offset >= bytes.length) {
|
||||
break;
|
||||
}
|
||||
bytes[offset] |= digit << (8 - index);
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
public static byte[] decode(String base32) {
|
||||
return Base32Codec.INSTANCE.decode(base32);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -183,22 +107,42 @@ public class Base32 {
|
||||
/**
|
||||
* base32解码
|
||||
*
|
||||
* @param source 被解码的base32字符串
|
||||
* @param charset 字符集
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String decodeStr(String source, String charset) {
|
||||
return StrUtil.str(decode(source), charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* base32解码
|
||||
*
|
||||
* @param source 被解码的base32字符串
|
||||
* @param source 被解码的base32字符串
|
||||
* @param charset 字符集
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String decodeStr(String source, Charset charset) {
|
||||
return StrUtil.str(decode(source), charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解码
|
||||
*
|
||||
* @param base32 base32编码
|
||||
* @return 数据
|
||||
*/
|
||||
public static byte[] decodeHex(String base32) {
|
||||
return Base32Codec.INSTANCE.decode(base32, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* base32解码
|
||||
*
|
||||
* @param source 被解码的base32字符串
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String decodeStrHex(String source) {
|
||||
return decodeStrHex(source, CharsetUtil.CHARSET_UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* base32解码
|
||||
*
|
||||
* @param source 被解码的base32字符串
|
||||
* @param charset 字符集
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String decodeStrHex(String source, Charset charset) {
|
||||
return StrUtil.str(decodeHex(source), charset);
|
||||
}
|
||||
}
|
||||
|
215
hutool-core/src/main/java/cn/hutool/core/codec/Base32Codec.java
Executable file
215
hutool-core/src/main/java/cn/hutool/core/codec/Base32Codec.java
Executable file
@ -0,0 +1,215 @@
|
||||
package cn.hutool.core.codec;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Base32 - encodes and decodes RFC4648 Base32 (see https://datatracker.ietf.org/doc/html/rfc4648#section-6 )<br>
|
||||
* base32就是用32(2的5次方)个特定ASCII码来表示256个ASCII码。<br>
|
||||
* 所以,5个ASCII字符经过base32编码后会变为8个字符(公约数为40),长度增加3/5.不足8n用“=”补足。<br>
|
||||
* 根据RFC4648 Base32规范,支持两种模式:
|
||||
* <ul>
|
||||
* <li>Base 32 Alphabet (ABCDEFGHIJKLMNOPQRSTUVWXYZ234567)</li>
|
||||
* <li>"Extended Hex" Base 32 Alphabet (0123456789ABCDEFGHIJKLMNOPQRSTUV)</li>
|
||||
* </ul>
|
||||
*
|
||||
* @author Looly
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public class Base32Codec implements Encoder<byte[], String>, Decoder<CharSequence, byte[]> {
|
||||
|
||||
public static Base32Codec INSTANCE = new Base32Codec();
|
||||
|
||||
@Override
|
||||
public String encode(byte[] data) {
|
||||
return encode(data, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码数据
|
||||
*
|
||||
* @param data 数据
|
||||
* @param useHex 是否使用Hex Alphabet
|
||||
* @return 编码后的Base32字符串
|
||||
*/
|
||||
public String encode(byte[] data, boolean useHex) {
|
||||
final Base32Encoder encoder = useHex ? Base32Encoder.HEX_ENCODER : Base32Encoder.ENCODER;
|
||||
return encoder.encode(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] decode(CharSequence encoded) {
|
||||
return decode(encoded, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解码数据
|
||||
*
|
||||
* @param encoded base32字符串
|
||||
* @param useHex 是否使用Hex Alphabet
|
||||
* @return 解码后的内容
|
||||
*/
|
||||
public byte[] decode(CharSequence encoded, boolean useHex) {
|
||||
final Base32Decoder decoder = useHex ? Base32Decoder.HEX_DECODER : Base32Decoder.DECODER;
|
||||
return decoder.decode(encoded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bas32编码器
|
||||
*/
|
||||
public static class Base32Encoder implements Encoder<byte[], String> {
|
||||
private static final String DEFAULT_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
|
||||
private static final String HEX_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUV";
|
||||
private static final Character DEFAULT_PAD = '=';
|
||||
private static final int[] BASE32_FILL = {-1, 4, 1, 6, 3};
|
||||
|
||||
public static final Base32Encoder ENCODER = new Base32Encoder(DEFAULT_ALPHABET, DEFAULT_PAD);
|
||||
public static final Base32Encoder HEX_ENCODER = new Base32Encoder(HEX_ALPHABET, DEFAULT_PAD);
|
||||
|
||||
private final char[] alphabet;
|
||||
private final Character pad;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param alphabet 自定义编码字母表,见 {@link #DEFAULT_ALPHABET}和 {@link #HEX_ALPHABET}
|
||||
* @param pad 补位字符
|
||||
*/
|
||||
public Base32Encoder(String alphabet, Character pad) {
|
||||
this.alphabet = alphabet.toCharArray();
|
||||
this.pad = pad;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(byte[] data) {
|
||||
int i = 0;
|
||||
int index = 0;
|
||||
int digit;
|
||||
int currByte;
|
||||
int nextByte;
|
||||
|
||||
int encodeLen = data.length * 8 / 5;
|
||||
if (encodeLen != 0) {
|
||||
encodeLen = encodeLen + 1 + BASE32_FILL[(data.length * 8) % 5];
|
||||
}
|
||||
|
||||
StringBuilder base32 = new StringBuilder(encodeLen);
|
||||
|
||||
while (i < data.length) {
|
||||
// unsign
|
||||
currByte = (data[i] >= 0) ? data[i] : (data[i] + 256);
|
||||
|
||||
/* Is the current digit going to span a byte boundary? */
|
||||
if (index > 3) {
|
||||
if ((i + 1) < data.length) {
|
||||
nextByte = (data[i + 1] >= 0) ? data[i + 1] : (data[i + 1] + 256);
|
||||
} else {
|
||||
nextByte = 0;
|
||||
}
|
||||
|
||||
digit = currByte & (0xFF >> index);
|
||||
index = (index + 5) % 8;
|
||||
digit <<= index;
|
||||
digit |= nextByte >> (8 - index);
|
||||
i++;
|
||||
} else {
|
||||
digit = (currByte >> (8 - (index + 5))) & 0x1F;
|
||||
index = (index + 5) % 8;
|
||||
if (index == 0) {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
base32.append(alphabet[digit]);
|
||||
}
|
||||
|
||||
if (null != pad) {
|
||||
// 末尾补充不足长度的
|
||||
while (base32.length() < encodeLen) {
|
||||
base32.append(pad.charValue());
|
||||
}
|
||||
}
|
||||
|
||||
return base32.toString();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base32解码器
|
||||
*/
|
||||
public static class Base32Decoder implements Decoder<CharSequence, byte[]> {
|
||||
private static final char BASE_CHAR = '0';
|
||||
|
||||
public static final Base32Decoder DECODER = new Base32Decoder(Base32Encoder.DEFAULT_ALPHABET);
|
||||
public static final Base32Decoder HEX_DECODER = new Base32Decoder(Base32Encoder.HEX_ALPHABET);
|
||||
|
||||
private final byte[] lookupTable;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param alphabet 编码字母表
|
||||
*/
|
||||
public Base32Decoder(String alphabet) {
|
||||
lookupTable = new byte[128];
|
||||
Arrays.fill(lookupTable, (byte) -1);
|
||||
|
||||
final int length = alphabet.length();
|
||||
|
||||
char c;
|
||||
for (int i = 0; i < length; i++) {
|
||||
c = alphabet.charAt(i);
|
||||
lookupTable[c - BASE_CHAR] = (byte) i;
|
||||
// 支持小写字母解码
|
||||
if(c >= 'A' && c <= 'Z'){
|
||||
lookupTable[Character.toLowerCase(c) - BASE_CHAR] = (byte) i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] decode(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];
|
||||
|
||||
for (i = 0, index = 0, offset = 0; i < base32.length(); i++) {
|
||||
lookup = base32.charAt(i) - BASE_CHAR;
|
||||
|
||||
/* Skip chars outside the lookup table */
|
||||
if (lookup < 0 || lookup >= lookupTable.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
digit = lookupTable[lookup];
|
||||
|
||||
/* If this digit is not in the table, ignore it */
|
||||
if (digit < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (index <= 3) {
|
||||
index = (index + 5) % 8;
|
||||
if (index == 0) {
|
||||
bytes[offset] |= digit;
|
||||
offset++;
|
||||
if (offset >= bytes.length) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
bytes[offset] |= digit << (8 - index);
|
||||
}
|
||||
} else {
|
||||
index = (index + 5) % 8;
|
||||
bytes[offset] |= (digit >>> index);
|
||||
offset++;
|
||||
|
||||
if (offset >= bytes.length) {
|
||||
break;
|
||||
}
|
||||
bytes[offset] |= digit << (8 - index);
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
}
|
||||
}
|
@ -15,10 +15,6 @@ public class Base58Codec implements Encoder<byte[], String>, Decoder<CharSequenc
|
||||
|
||||
public static Base58Codec INSTANCE = new Base58Codec();
|
||||
|
||||
private final char[] alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
|
||||
private final char ENCODED_ZERO = alphabet[0];
|
||||
private final int[] lookup = initLookup();
|
||||
|
||||
/**
|
||||
* Base58编码
|
||||
*
|
||||
@ -27,36 +23,7 @@ public class Base58Codec implements Encoder<byte[], String>, Decoder<CharSequenc
|
||||
*/
|
||||
@Override
|
||||
public String encode(byte[] data) {
|
||||
if (null == data) {
|
||||
return null;
|
||||
}
|
||||
if (data.length == 0) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
// 计算开头0的个数
|
||||
int zeroCount = 0;
|
||||
while (zeroCount < data.length && data[zeroCount] == 0) {
|
||||
++zeroCount;
|
||||
}
|
||||
// 将256位编码转换为58位编码
|
||||
data = Arrays.copyOf(data, data.length); // since we modify it in-place
|
||||
final char[] encoded = new char[data.length * 2]; // upper bound
|
||||
int outputStart = encoded.length;
|
||||
for (int inputStart = zeroCount; inputStart < data.length; ) {
|
||||
encoded[--outputStart] = alphabet[divmod(data, inputStart, 256, 58)];
|
||||
if (data[inputStart] == 0) {
|
||||
++inputStart; // optimization - skip leading zeros
|
||||
}
|
||||
}
|
||||
// Preserve exactly as many leading encoded zeros in output as there were leading zeros in input.
|
||||
while (outputStart < encoded.length && encoded[outputStart] == ENCODED_ZERO) {
|
||||
++outputStart;
|
||||
}
|
||||
while (--zeroCount >= 0) {
|
||||
encoded[--outputStart] = ENCODED_ZERO;
|
||||
}
|
||||
// Return encoded string (including encoded leading zeros).
|
||||
return new String(encoded, outputStart, encoded.length - outputStart);
|
||||
return Base58Encoder.ENCODER.encode(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -68,52 +35,130 @@ public class Base58Codec implements Encoder<byte[], String>, Decoder<CharSequenc
|
||||
*/
|
||||
@Override
|
||||
public byte[] decode(CharSequence encoded) throws IllegalArgumentException {
|
||||
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 ? lookup[c] : -1;
|
||||
if (digit < 0) {
|
||||
throw new IllegalArgumentException(StrUtil.format("Invalid char '{}' at [{}]", c, i));
|
||||
}
|
||||
input58[i] = (byte) digit;
|
||||
}
|
||||
// Count leading zeros.
|
||||
int zeros = 0;
|
||||
while (zeros < input58.length && input58[zeros] == 0) {
|
||||
++zeros;
|
||||
}
|
||||
// Convert base-58 digits to base-256 digits.
|
||||
byte[] decoded = new byte[encoded.length()];
|
||||
int outputStart = decoded.length;
|
||||
for (int inputStart = zeros; inputStart < input58.length; ) {
|
||||
decoded[--outputStart] = divmod(input58, inputStart, 58, 256);
|
||||
if (input58[inputStart] == 0) {
|
||||
++inputStart; // optimization - skip leading zeros
|
||||
}
|
||||
}
|
||||
// Ignore extra leading zeroes that were added during the calculation.
|
||||
while (outputStart < decoded.length && decoded[outputStart] == 0) {
|
||||
++outputStart;
|
||||
}
|
||||
// Return decoded data (including original number of leading zeros).
|
||||
return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.length);
|
||||
return Base58Decoder.DECODER.decode(encoded);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化字符序号查找表
|
||||
* Base58编码器
|
||||
*
|
||||
* @return 字符序号查找表
|
||||
* @since 5.8.0
|
||||
*/
|
||||
private int[] initLookup() {
|
||||
final int[] lookup = new int['z' + 1];
|
||||
Arrays.fill(lookup, -1);
|
||||
for (int i = 0; i < alphabet.length; i++)
|
||||
lookup[alphabet[i]] = i;
|
||||
return lookup;
|
||||
public static class Base58Encoder implements Encoder<byte[], String> {
|
||||
private static final String DEFAULT_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
||||
|
||||
public static final Base58Encoder ENCODER = new Base58Encoder(DEFAULT_ALPHABET.toCharArray());
|
||||
|
||||
private final char[] alphabet;
|
||||
private final char alphabetZero;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param alphabet 编码字母表
|
||||
*/
|
||||
public Base58Encoder(char[] alphabet) {
|
||||
this.alphabet = alphabet;
|
||||
alphabetZero = alphabet[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String encode(byte[] data) {
|
||||
if (null == data) {
|
||||
return null;
|
||||
}
|
||||
if (data.length == 0) {
|
||||
return StrUtil.EMPTY;
|
||||
}
|
||||
// 计算开头0的个数
|
||||
int zeroCount = 0;
|
||||
while (zeroCount < data.length && data[zeroCount] == 0) {
|
||||
++zeroCount;
|
||||
}
|
||||
// 将256位编码转换为58位编码
|
||||
data = Arrays.copyOf(data, data.length); // since we modify it in-place
|
||||
final char[] encoded = new char[data.length * 2]; // upper bound
|
||||
int outputStart = encoded.length;
|
||||
for (int inputStart = zeroCount; inputStart < data.length; ) {
|
||||
encoded[--outputStart] = alphabet[divmod(data, inputStart, 256, 58)];
|
||||
if (data[inputStart] == 0) {
|
||||
++inputStart; // optimization - skip leading zeros
|
||||
}
|
||||
}
|
||||
// Preserve exactly as many leading encoded zeros in output as there were leading zeros in input.
|
||||
while (outputStart < encoded.length && encoded[outputStart] == alphabetZero) {
|
||||
++outputStart;
|
||||
}
|
||||
while (--zeroCount >= 0) {
|
||||
encoded[--outputStart] = alphabetZero;
|
||||
}
|
||||
// Return encoded string (including encoded leading zeros).
|
||||
return new String(encoded, outputStart, encoded.length - outputStart);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base58解码器
|
||||
*
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public static class Base58Decoder implements Decoder<CharSequence, byte[]> {
|
||||
|
||||
public static Base58Decoder DECODER = new Base58Decoder(Base58Encoder.DEFAULT_ALPHABET);
|
||||
|
||||
private final byte[] lookupTable;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param alphabet 编码字符表
|
||||
*/
|
||||
public Base58Decoder(String alphabet) {
|
||||
final byte[] lookupTable = new byte['z' + 1];
|
||||
Arrays.fill(lookupTable, (byte) -1);
|
||||
|
||||
final int length = alphabet.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
lookupTable[alphabet.charAt(i)] = (byte) i;
|
||||
}
|
||||
this.lookupTable = lookupTable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] decode(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;
|
||||
if (digit < 0) {
|
||||
throw new IllegalArgumentException(StrUtil.format("Invalid char '{}' at [{}]", c, i));
|
||||
}
|
||||
input58[i] = (byte) digit;
|
||||
}
|
||||
// Count leading zeros.
|
||||
int zeros = 0;
|
||||
while (zeros < input58.length && input58[zeros] == 0) {
|
||||
++zeros;
|
||||
}
|
||||
// Convert base-58 digits to base-256 digits.
|
||||
byte[] decoded = new byte[encoded.length()];
|
||||
int outputStart = decoded.length;
|
||||
for (int inputStart = zeros; inputStart < input58.length; ) {
|
||||
decoded[--outputStart] = divmod(input58, inputStart, 58, 256);
|
||||
if (input58[inputStart] == 0) {
|
||||
++inputStart; // optimization - skip leading zeros
|
||||
}
|
||||
}
|
||||
// Ignore extra leading zeroes that were added during the calculation.
|
||||
while (outputStart < decoded.length && decoded[outputStart] == 0) {
|
||||
++outputStart;
|
||||
}
|
||||
// Return decoded data (including original number of leading zeros).
|
||||
return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,15 +1,15 @@
|
||||
package cn.hutool.core.codec;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* Base62工具类,提供Base62的编码和解码方案<br>
|
||||
*
|
||||
@ -19,7 +19,6 @@ import cn.hutool.core.util.StrUtil;
|
||||
public class Base62 {
|
||||
|
||||
private static final Charset DEFAULT_CHARSET = CharsetUtil.CHARSET_UTF_8;
|
||||
private static final Base62Codec CODEC = Base62Codec.createGmp();
|
||||
|
||||
// -------------------------------------------------------------------- encode
|
||||
/**
|
||||
@ -50,7 +49,7 @@ public class Base62 {
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String encode(byte[] source) {
|
||||
return new String(CODEC.encode(source));
|
||||
return new String(Base62Codec.INSTANCE.encode(source));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,6 +72,57 @@ public class Base62 {
|
||||
return encode(FileUtil.readBytes(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* Base62编码(反转字母表模式)
|
||||
*
|
||||
* @param source 被编码的Base62字符串
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String encodeInverted(CharSequence source) {
|
||||
return encodeInverted(source, DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base62编码(反转字母表模式)
|
||||
*
|
||||
* @param source 被编码的Base62字符串
|
||||
* @param charset 字符集
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String encodeInverted(CharSequence source, Charset charset) {
|
||||
return encodeInverted(StrUtil.bytes(source, charset));
|
||||
}
|
||||
|
||||
/**
|
||||
* Base62编码(反转字母表模式)
|
||||
*
|
||||
* @param source 被编码的Base62字符串
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String encodeInverted(byte[] source) {
|
||||
return new String(Base62Codec.INSTANCE.encode(source, true));
|
||||
}
|
||||
|
||||
/**
|
||||
* Base62编码
|
||||
*
|
||||
* @param in 被编码Base62的流(一般为图片流或者文件流)
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String encodeInverted(InputStream in) {
|
||||
return encodeInverted(IoUtil.readBytes(in));
|
||||
}
|
||||
|
||||
/**
|
||||
* Base62编码(反转字母表模式)
|
||||
*
|
||||
* @param file 被编码Base62的文件
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String encodeInverted(File file) {
|
||||
return encodeInverted(FileUtil.readBytes(file));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------- decode
|
||||
/**
|
||||
* Base62解码
|
||||
@ -144,6 +194,69 @@ public class Base62 {
|
||||
* @return 解码后的bytes
|
||||
*/
|
||||
public static byte[] decode(byte[] base62bytes) {
|
||||
return CODEC.decode(base62bytes);
|
||||
return Base62Codec.INSTANCE.decode(base62bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base62解码(反转字母表模式)
|
||||
*
|
||||
* @param source 被解码的Base62字符串
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String decodeStrInverted(CharSequence source) {
|
||||
return decodeStrInverted(source, DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base62解码(反转字母表模式)
|
||||
*
|
||||
* @param source 被解码的Base62字符串
|
||||
* @param charset 字符集
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static String decodeStrInverted(CharSequence source, Charset charset) {
|
||||
return StrUtil.str(decodeInverted(source), charset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base62解码(反转字母表模式)
|
||||
*
|
||||
* @param Base62 被解码的Base62字符串
|
||||
* @param destFile 目标文件
|
||||
* @return 目标文件
|
||||
*/
|
||||
public static File decodeToFileInverted(CharSequence Base62, File destFile) {
|
||||
return FileUtil.writeBytes(decodeInverted(Base62), destFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base62解码(反转字母表模式)
|
||||
*
|
||||
* @param base62Str 被解码的Base62字符串
|
||||
* @param out 写出到的流
|
||||
* @param isCloseOut 是否关闭输出流
|
||||
*/
|
||||
public static void decodeToStreamInverted(CharSequence base62Str, OutputStream out, boolean isCloseOut) {
|
||||
IoUtil.write(out, isCloseOut, decodeInverted(base62Str));
|
||||
}
|
||||
|
||||
/**
|
||||
* Base62解码(反转字母表模式)
|
||||
*
|
||||
* @param base62Str 被解码的Base62字符串
|
||||
* @return 被加密后的字符串
|
||||
*/
|
||||
public static byte[] decodeInverted(CharSequence base62Str) {
|
||||
return decodeInverted(StrUtil.bytes(base62Str, DEFAULT_CHARSET));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解码Base62(反转字母表模式)
|
||||
*
|
||||
* @param base62bytes Base62输入
|
||||
* @return 解码后的bytes
|
||||
*/
|
||||
public static byte[] decodeInverted(byte[] base62bytes) {
|
||||
return Base62Codec.INSTANCE.decode(base62bytes, true);
|
||||
}
|
||||
}
|
||||
|
@ -12,73 +12,13 @@ import java.io.Serializable;
|
||||
* @author Looly, Sebastian Ruhleder, sebastian@seruco.io
|
||||
* @since 4.5.9
|
||||
*/
|
||||
public class Base62Codec implements Encoder<byte[], byte[]>, Decoder<byte[], byte[]>, Serializable{
|
||||
public class Base62Codec implements Encoder<byte[], byte[]>, Decoder<byte[], byte[]>, Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private static final int STANDARD_BASE = 256;
|
||||
private static final int TARGET_BASE = 62;
|
||||
|
||||
/**
|
||||
* 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风格的Base62编码解码器对象
|
||||
*
|
||||
* @return Base62Codec
|
||||
*/
|
||||
public static Base62Codec createGmp() {
|
||||
return new Base62Codec(GMP);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Inverted风格的Base62编码解码器对象
|
||||
*
|
||||
* @return Base62Codec
|
||||
*/
|
||||
public static Base62Codec createInverted() {
|
||||
return new Base62Codec(INVERTED);
|
||||
}
|
||||
|
||||
private final byte[] alphabet;
|
||||
private final byte[] lookup;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param alphabet 自定义字母表
|
||||
*/
|
||||
public Base62Codec(byte[] alphabet) {
|
||||
this.alphabet = alphabet;
|
||||
lookup = new byte[256];
|
||||
for (int i = 0; i < alphabet.length; i++) {
|
||||
lookup[alphabet[i]] = (byte) (i & 0xFF);
|
||||
}
|
||||
}
|
||||
public static Base62Codec INSTANCE = new Base62Codec();
|
||||
|
||||
/**
|
||||
* 编码指定消息bytes为Base62格式的bytes
|
||||
@ -88,8 +28,19 @@ public class Base62Codec implements Encoder<byte[], byte[]>, Decoder<byte[], byt
|
||||
*/
|
||||
@Override
|
||||
public byte[] encode(byte[] data) {
|
||||
final byte[] indices = convert(data, STANDARD_BASE, TARGET_BASE);
|
||||
return translate(indices, alphabet);
|
||||
return encode(data, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码指定消息bytes为Base62格式的bytes
|
||||
*
|
||||
* @param data 被编码的消息
|
||||
* @param useInverted 是否使用反转风格,即将GMP风格中的大小写做转换
|
||||
* @return Base62内容
|
||||
*/
|
||||
public byte[] encode(byte[] data, boolean useInverted) {
|
||||
final Base62Encoder encoder = useInverted ? Base62Encoder.INVERTED_ENCODER : Base62Encoder.GMP_ENCODER;
|
||||
return encoder.encode(data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -100,15 +51,114 @@ public class Base62Codec implements Encoder<byte[], byte[]>, Decoder<byte[], byt
|
||||
*/
|
||||
@Override
|
||||
public byte[] decode(byte[] encoded) {
|
||||
final byte[] prepared = translate(encoded, lookup);
|
||||
return convert(prepared, TARGET_BASE, STANDARD_BASE);
|
||||
return decode(encoded, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解码Base62消息
|
||||
*
|
||||
* @param encoded Base62内容
|
||||
* @param useInverted 是否使用反转风格,即将GMP风格中的大小写做转换
|
||||
* @return 消息
|
||||
*/
|
||||
public byte[] decode(byte[] encoded, boolean useInverted) {
|
||||
final Base62Decoder decoder = useInverted ? Base62Decoder.INVERTED_DECODER : Base62Decoder.GMP_DECODER;
|
||||
return decoder.decode(encoded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base62编码器
|
||||
*
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public static class Base62Encoder implements Encoder<byte[], byte[]> {
|
||||
/**
|
||||
* 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' //
|
||||
};
|
||||
|
||||
public static Base62Encoder GMP_ENCODER = new Base62Encoder(GMP);
|
||||
public static Base62Encoder INVERTED_ENCODER = new Base62Encoder(INVERTED);
|
||||
|
||||
private final byte[] alphabet;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param alphabet 字符表
|
||||
*/
|
||||
public Base62Encoder(byte[] alphabet) {
|
||||
this.alphabet = alphabet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] encode(byte[] data) {
|
||||
final byte[] indices = convert(data, STANDARD_BASE, TARGET_BASE);
|
||||
return translate(indices, alphabet);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base62解码器
|
||||
*
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public static class Base62Decoder implements Decoder<byte[], byte[]> {
|
||||
|
||||
public static Base62Decoder GMP_DECODER = new Base62Decoder(Base62Encoder.GMP);
|
||||
public static Base62Decoder INVERTED_DECODER = new Base62Decoder(Base62Encoder.INVERTED);
|
||||
|
||||
private final byte[] lookupTable;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param alphabet 字母表
|
||||
*/
|
||||
public Base62Decoder(byte[] alphabet) {
|
||||
lookupTable = new byte['z' + 1];
|
||||
for (int i = 0; i < alphabet.length; i++) {
|
||||
lookupTable[alphabet[i]] = (byte) i;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public byte[] decode(byte[] encoded) {
|
||||
final byte[] prepared = translate(encoded, lookupTable);
|
||||
return convert(prepared, TARGET_BASE, STANDARD_BASE);
|
||||
}
|
||||
}
|
||||
|
||||
// region Private Methods
|
||||
|
||||
/**
|
||||
* 按照字典转换bytes
|
||||
*
|
||||
* @param indices 内容
|
||||
* @param indices 内容
|
||||
* @param dictionary 字典
|
||||
* @return 转换值
|
||||
*/
|
||||
@ -125,7 +175,7 @@ public class Base62Codec implements Encoder<byte[], byte[]>, Decoder<byte[], byt
|
||||
/**
|
||||
* 使用定义的字母表从源基准到目标基准
|
||||
*
|
||||
* @param message 消息bytes
|
||||
* @param message 消息bytes
|
||||
* @param sourceBase 源基准长度
|
||||
* @param targetBase 目标基准长度
|
||||
* @return 计算结果
|
||||
@ -171,8 +221,8 @@ public class Base62Codec implements Encoder<byte[], byte[]>, Decoder<byte[], byt
|
||||
* 估算结果长度
|
||||
*
|
||||
* @param inputLength 输入长度
|
||||
* @param sourceBase 源基准长度
|
||||
* @param targetBase 目标基准长度
|
||||
* @param sourceBase 源基准长度
|
||||
* @param targetBase 目标基准长度
|
||||
* @return 估算长度
|
||||
*/
|
||||
private static int estimateOutputLength(int inputLength, int sourceBase, int targetBase) {
|
||||
|
@ -217,7 +217,7 @@ public class Base64 {
|
||||
* @since 5.7.2
|
||||
*/
|
||||
public static String encodeStr(byte[] arr, boolean isMultiLine, boolean isUrlSafe) {
|
||||
return Base64Encoder.encodeStr(arr, isMultiLine, isUrlSafe);
|
||||
return StrUtil.str(encode(arr, isMultiLine, isUrlSafe), DEFAULT_CHARSET);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,6 @@
|
||||
package cn.hutool.core.codec;
|
||||
|
||||
import cn.hutool.core.lang.mutable.MutableInt;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@ -87,7 +88,7 @@ public class Base64Decoder {
|
||||
return in;
|
||||
}
|
||||
|
||||
final IntWrapper offset = new IntWrapper(pos);
|
||||
final MutableInt offset = new MutableInt(pos);
|
||||
|
||||
byte sestet0;
|
||||
byte sestet1;
|
||||
@ -96,7 +97,7 @@ public class Base64Decoder {
|
||||
int maxPos = pos + length - 1;
|
||||
int octetId = 0;
|
||||
byte[] octet = new byte[length * 3 / 4];// over-estimated if non-base64 characters present
|
||||
while (offset.value <= maxPos) {
|
||||
while (offset.intValue() <= maxPos) {
|
||||
sestet0 = getNextValidDecodeByte(in, offset, maxPos);
|
||||
sestet1 = getNextValidDecodeByte(in, offset, maxPos);
|
||||
sestet2 = getNextValidDecodeByte(in, offset, maxPos);
|
||||
@ -141,11 +142,12 @@ public class Base64Decoder {
|
||||
* @param maxPos 最大位置
|
||||
* @return 有效字符,如果达到末尾返回
|
||||
*/
|
||||
private static byte getNextValidDecodeByte(byte[] in, IntWrapper pos, int maxPos) {
|
||||
private static byte getNextValidDecodeByte(byte[] in, MutableInt pos, int maxPos) {
|
||||
byte base64Byte;
|
||||
byte decodeByte;
|
||||
while (pos.value <= maxPos) {
|
||||
base64Byte = in[pos.value++];
|
||||
while (pos.intValue() <= maxPos) {
|
||||
base64Byte = in[pos.intValue()];
|
||||
pos.increment();
|
||||
if (base64Byte > -1) {
|
||||
decodeByte = DECODE_TABLE[base64Byte];
|
||||
if (decodeByte > -1) {
|
||||
@ -156,19 +158,5 @@ public class Base64Decoder {
|
||||
// padding if reached max position
|
||||
return PADDING;
|
||||
}
|
||||
|
||||
/**
|
||||
* int包装,使之可变
|
||||
*
|
||||
* @author looly
|
||||
*
|
||||
*/
|
||||
private static class IntWrapper {
|
||||
int value;
|
||||
|
||||
IntWrapper(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
// ----------------------------------------------------------------------------------------------- Private end
|
||||
}
|
||||
|
@ -27,11 +27,11 @@ public class PunyCode {
|
||||
/**
|
||||
* 将内容编码为PunyCode
|
||||
*
|
||||
* @param input 字符串
|
||||
* @param input 字符串
|
||||
* @return PunyCode字符串
|
||||
* @throws UtilException 计算异常
|
||||
*/
|
||||
public static String encode(String input) throws UtilException {
|
||||
public static String encode(CharSequence input) throws UtilException {
|
||||
return encode(input, false);
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ public class PunyCode {
|
||||
* @return PunyCode字符串
|
||||
* @throws UtilException 计算异常
|
||||
*/
|
||||
public static String encode(String input, boolean withPrefix) throws UtilException {
|
||||
public static String encode(CharSequence input, boolean withPrefix) throws UtilException {
|
||||
int n = INITIAL_N;
|
||||
int delta = 0;
|
||||
int bias = INITIAL_BIAS;
|
||||
@ -112,7 +112,7 @@ public class PunyCode {
|
||||
n++;
|
||||
}
|
||||
|
||||
if(withPrefix){
|
||||
if (withPrefix) {
|
||||
output.insert(0, PUNY_CODE_PREFIX);
|
||||
}
|
||||
return output.toString();
|
||||
@ -214,6 +214,7 @@ public class PunyCode {
|
||||
* ...
|
||||
* 35 -> '9'
|
||||
* </pre>
|
||||
*
|
||||
* @param d 输入字符
|
||||
* @return 转换后的字符
|
||||
* @throws UtilException 无效字符
|
||||
@ -242,6 +243,7 @@ public class PunyCode {
|
||||
* ...
|
||||
* '9' -> 35
|
||||
* </pre>
|
||||
*
|
||||
* @param c 输入字符
|
||||
* @return 转换后的字符
|
||||
* @throws UtilException 无效字符
|
||||
|
@ -1756,7 +1756,7 @@ public class DateUtil extends CalendarUtil {
|
||||
* @return 是否闰年
|
||||
*/
|
||||
public static boolean isLeapYear(int year) {
|
||||
return new GregorianCalendar().isLeapYear(year);
|
||||
return Year.isLeap(year);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -29,7 +29,7 @@ import cn.hutool.core.lang.func.Func0;
|
||||
import cn.hutool.core.lang.func.VoidFunc0;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Collection;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
@ -108,7 +108,7 @@ public class Opt<T> {
|
||||
* @return 一个包裹里元素可能为空的 {@code Opt}
|
||||
* @since 5.7.17
|
||||
*/
|
||||
public static <T> Opt<List<T>> ofEmptyAble(List<T> value) {
|
||||
public static <T, R extends Collection<T>> Opt<R> ofEmptyAble(R value) {
|
||||
return CollectionUtil.isEmpty(value) ? empty() : new Opt<>(value);
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,18 @@ public class LazyFunLoader<T> extends LazyLoader<T> {
|
||||
*/
|
||||
private Supplier<T> supplier;
|
||||
|
||||
/**
|
||||
* 静态工厂方法,提供语义性与编码便利性
|
||||
* @param supplier 用于生成对象的函数
|
||||
* @param <T> 对象类型
|
||||
* @return 函数式懒加载加载器对象
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public static <T> LazyFunLoader<T> on(final Supplier<T> supplier) {
|
||||
Assert.notNull(supplier, "supplier must be not null!");
|
||||
return new LazyFunLoader<>(supplier);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
|
@ -152,12 +152,12 @@ public class MutableInt extends Number implements Comparable<MutableInt>, Mutabl
|
||||
* 相等需同时满足如下条件:
|
||||
* <ol>
|
||||
* <li>非空</li>
|
||||
* <li>类型为 {@link MutableInt}</li>
|
||||
* <li>类型为 MutableInt</li>
|
||||
* <li>值相等</li>
|
||||
* </ol>
|
||||
*
|
||||
* @param obj 比对的对象
|
||||
* @return 相同返回<code>true</code>,否则 <code>false</code>
|
||||
* @return 相同返回<code>true</code>,否则 {@code false}
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object obj) {
|
||||
@ -176,7 +176,7 @@ public class MutableInt extends Number implements Comparable<MutableInt>, Mutabl
|
||||
/**
|
||||
* 比较
|
||||
*
|
||||
* @param other 其它 {@link MutableInt} 对象
|
||||
* @param other 其它 MutableInt 对象
|
||||
* @return x==y返回0,x<y返回-1,x>y返回1
|
||||
*/
|
||||
@Override
|
||||
|
@ -137,6 +137,7 @@ public class AntPathMatcher {
|
||||
*
|
||||
* @param cachePatterns 是否缓存表达式
|
||||
* @see #getStringMatcher(String)
|
||||
* @return this
|
||||
*/
|
||||
public AntPathMatcher setCachePatterns(boolean cachePatterns) {
|
||||
this.cachePatterns = cachePatterns;
|
||||
|
@ -1,6 +1,7 @@
|
||||
package cn.hutool.core.codec;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -14,6 +15,24 @@ public class Base32Test {
|
||||
|
||||
String decodeStr = Base32.decodeStr(encode);
|
||||
Assert.assertEquals(a, decodeStr);
|
||||
|
||||
// 支持小写模式解码
|
||||
decodeStr = Base32.decodeStr(encode.toLowerCase());
|
||||
Assert.assertEquals(a, decodeStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hexEncodeAndDecodeTest(){
|
||||
String a = "伦家是一个非常长的字符串";
|
||||
String encode = Base32.encodeHex(StrUtil.utf8Bytes(a));
|
||||
Assert.assertEquals("SIUADPDEMRJ9HBV4N20E9E5AT6EPTPDON3KPBFV7JA2EBBCNSUMADP5OM8======", encode);
|
||||
|
||||
String decodeStr = Base32.decodeStrHex(encode);
|
||||
Assert.assertEquals(a, decodeStr);
|
||||
|
||||
// 支持小写模式解码
|
||||
decodeStr = Base32.decodeStrHex(encode.toLowerCase());
|
||||
Assert.assertEquals(a, decodeStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1,5 +1,6 @@
|
||||
package cn.hutool.core.codec;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
@ -20,4 +21,30 @@ public class Base62Test {
|
||||
String decodeStr = Base62.decodeStr(encode);
|
||||
Assert.assertEquals(a, decodeStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodeAndDecodeInvertedTest() {
|
||||
String a = "伦家是一个非常长的字符串66";
|
||||
String encode = Base62.encodeInverted(a);
|
||||
Assert.assertEquals("17Vku8w4jmg8Dqf8LK9vnNKDmoEwN4RjmVA6f0xSlRRt53IkbNQO", encode);
|
||||
|
||||
String decodeStr = Base62.decodeStrInverted(encode);
|
||||
Assert.assertEquals(a, decodeStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodeAndDecodeRandomTest() {
|
||||
String a = RandomUtil.randomString(RandomUtil.randomInt(1000));
|
||||
String encode = Base62.encode(a);
|
||||
String decodeStr = Base62.decodeStr(encode);
|
||||
Assert.assertEquals(a, decodeStr);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void encodeAndDecodeInvertedRandomTest() {
|
||||
String a = RandomUtil.randomString(RandomUtil.randomInt(1000));
|
||||
String encode = Base62.encodeInverted(a);
|
||||
String decodeStr = Base62.decodeStrInverted(encode);
|
||||
Assert.assertEquals(a, decodeStr);
|
||||
}
|
||||
}
|
||||
|
@ -42,4 +42,32 @@ public class LazyFunLoaderTest {
|
||||
|
||||
Assert.assertFalse(loader.isInitialize());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLoadStaticFactoryMethod1() {
|
||||
|
||||
LazyFunLoader<BigObject> loader = LazyFunLoader.on(BigObject::new);
|
||||
|
||||
Assert.assertNotNull(loader.get());
|
||||
Assert.assertTrue(loader.isInitialize());
|
||||
|
||||
// 对于某些对象,在程序关闭时,需要进行销毁操作
|
||||
loader.ifInitialized(BigObject::destroy);
|
||||
|
||||
Assert.assertTrue(loader.get().isDestroy);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnLoadStaticFactoryMethod2() {
|
||||
|
||||
LazyFunLoader<BigObject> loader = LazyFunLoader.on(BigObject::new);
|
||||
|
||||
// 若从未使用,则可以避免不必要的初始化
|
||||
loader.ifInitialized(it -> {
|
||||
|
||||
Assert.fail();
|
||||
it.destroy();
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,10 @@
|
||||
package cn.hutool.cron.pattern;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.cron.CronException;
|
||||
import cn.hutool.cron.pattern.matcher.AlwaysTrueValueMatcher;
|
||||
import cn.hutool.cron.pattern.matcher.DayOfMonthValueMatcher;
|
||||
import cn.hutool.cron.pattern.matcher.ValueMatcher;
|
||||
import cn.hutool.cron.pattern.matcher.ValueMatcherBuilder;
|
||||
import cn.hutool.cron.pattern.parser.DayOfMonthValueParser;
|
||||
import cn.hutool.cron.pattern.parser.DayOfWeekValueParser;
|
||||
import cn.hutool.cron.pattern.parser.HourValueParser;
|
||||
import cn.hutool.cron.pattern.parser.MinuteValueParser;
|
||||
import cn.hutool.cron.pattern.parser.MonthValueParser;
|
||||
import cn.hutool.cron.pattern.parser.SecondValueParser;
|
||||
import cn.hutool.cron.pattern.parser.ValueParser;
|
||||
import cn.hutool.cron.pattern.parser.YearValueParser;
|
||||
import cn.hutool.cron.pattern.matcher.MatcherTable;
|
||||
import cn.hutool.cron.pattern.parser.CronPatternParser;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
|
||||
/**
|
||||
@ -32,14 +17,14 @@ import java.util.TimeZone;
|
||||
* <li><strong>月</strong> :范围:1~12,同时支持不区分大小写的别名:"jan","feb", "mar", "apr", "may","jun", "jul", "aug", "sep","oct", "nov", "dec"</li>
|
||||
* <li><strong>周</strong> :范围:0 (Sunday)~6(Saturday),7也可以表示周日,同时支持不区分大小写的别名:"sun","mon", "tue", "wed", "thu","fri", "sat",<strong>"L"</strong> 表示周六</li>
|
||||
* </ol>
|
||||
*
|
||||
* <p>
|
||||
* 为了兼容Quartz表达式,同时支持6位和7位表达式,其中:<br>
|
||||
*
|
||||
* <pre>
|
||||
* 当为6位时,第一位表示<strong>秒</strong> ,范围0~59,但是第一位不做匹配
|
||||
* 当为7位时,最后一位表示<strong>年</strong> ,范围1970~2099,但是第7位不做解析,也不做匹配
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* 当定时任务运行到的时间匹配这些表达式后,任务被启动。<br>
|
||||
* 注意:
|
||||
*
|
||||
@ -47,7 +32,7 @@ import java.util.TimeZone;
|
||||
* 当isMatchSecond为{@code true}时才会匹配秒部分
|
||||
* 默认都是关闭的
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* 对于每一个子表达式,同样支持以下形式:
|
||||
* <ul>
|
||||
* <li><strong>*</strong> :表示匹配这个位置所有的时间</li>
|
||||
@ -62,10 +47,10 @@ import java.util.TimeZone;
|
||||
* <pre>
|
||||
* 间隔(/) > 区间(-) > 列表(,)
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* 例如 2,3,6/3中,由于“/”优先级高,因此相当于2,3,(6/3),结果与 2,3,6等价<br>
|
||||
* <br>
|
||||
*
|
||||
* <p>
|
||||
* 一些例子:
|
||||
* <ul>
|
||||
* <li><strong>5 * * * *</strong> :每个点钟的5分执行,00:05,01:05……</li>
|
||||
@ -77,36 +62,11 @@ import java.util.TimeZone;
|
||||
* </ul>
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class CronPattern {
|
||||
|
||||
private static final ValueParser SECOND_VALUE_PARSER = new SecondValueParser();
|
||||
private static final ValueParser MINUTE_VALUE_PARSER = new MinuteValueParser();
|
||||
private static final ValueParser HOUR_VALUE_PARSER = new HourValueParser();
|
||||
private static final ValueParser DAY_OF_MONTH_VALUE_PARSER = new DayOfMonthValueParser();
|
||||
private static final ValueParser MONTH_VALUE_PARSER = new MonthValueParser();
|
||||
private static final ValueParser DAY_OF_WEEK_VALUE_PARSER = new DayOfWeekValueParser();
|
||||
private static final ValueParser YEAR_VALUE_PARSER = new YearValueParser();
|
||||
|
||||
private final String pattern;
|
||||
|
||||
/** 秒字段匹配列表 */
|
||||
private final List<ValueMatcher> secondMatchers = new ArrayList<>();
|
||||
/** 分字段匹配列表 */
|
||||
private final List<ValueMatcher> minuteMatchers = new ArrayList<>();
|
||||
/** 时字段匹配列表 */
|
||||
private final List<ValueMatcher> hourMatchers = new ArrayList<>();
|
||||
/** 每月几号字段匹配列表 */
|
||||
private final List<ValueMatcher> dayOfMonthMatchers = new ArrayList<>();
|
||||
/** 月字段匹配列表 */
|
||||
private final List<ValueMatcher> monthMatchers = new ArrayList<>();
|
||||
/** 星期字段匹配列表 */
|
||||
private final List<ValueMatcher> dayOfWeekMatchers = new ArrayList<>();
|
||||
/** 年字段匹配列表 */
|
||||
private final List<ValueMatcher> yearMatchers = new ArrayList<>();
|
||||
/** 匹配器个数,取决于复合任务表达式中的单一表达式个数 */
|
||||
private int matcherSize;
|
||||
private final MatcherTable matcherTable;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
@ -115,14 +75,15 @@ public class CronPattern {
|
||||
*/
|
||||
public CronPattern(String pattern) {
|
||||
this.pattern = pattern;
|
||||
parseGroupPattern(pattern);
|
||||
this.matcherTable = CronPatternParser.create().parse(pattern);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------- match start
|
||||
|
||||
/**
|
||||
* 给定时间是否匹配定时任务表达式
|
||||
*
|
||||
* @param millis 时间毫秒数
|
||||
* @param millis 时间毫秒数
|
||||
* @param isMatchSecond 是否匹配秒
|
||||
* @return 如果匹配返回 {@code true}, 否则返回 {@code false}
|
||||
*/
|
||||
@ -133,8 +94,8 @@ public class CronPattern {
|
||||
/**
|
||||
* 给定时间是否匹配定时任务表达式
|
||||
*
|
||||
* @param timezone 时区 {@link TimeZone}
|
||||
* @param millis 时间毫秒数
|
||||
* @param timezone 时区 {@link TimeZone}
|
||||
* @param millis 时间毫秒数
|
||||
* @param isMatchSecond 是否匹配秒
|
||||
* @return 如果匹配返回 {@code true}, 否则返回 {@code false}
|
||||
*/
|
||||
@ -147,12 +108,12 @@ public class CronPattern {
|
||||
/**
|
||||
* 给定时间是否匹配定时任务表达式
|
||||
*
|
||||
* @param calendar 时间
|
||||
* @param calendar 时间
|
||||
* @param isMatchSecond 是否匹配秒
|
||||
* @return 如果匹配返回 {@code true}, 否则返回 {@code false}
|
||||
*/
|
||||
public boolean match(GregorianCalendar calendar, boolean isMatchSecond) {
|
||||
final int second = calendar.get(Calendar.SECOND);
|
||||
final int second = isMatchSecond ? calendar.get(Calendar.SECOND) : -1;
|
||||
final int minute = calendar.get(Calendar.MINUTE);
|
||||
final int hour = calendar.get(Calendar.HOUR_OF_DAY);
|
||||
final int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
|
||||
@ -160,20 +121,7 @@ public class CronPattern {
|
||||
final int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK) - 1; // 星期从0开始,0和7都表示周日
|
||||
final int year = calendar.get(Calendar.YEAR);
|
||||
|
||||
boolean eval;
|
||||
for (int i = 0; i < matcherSize; i++) {
|
||||
eval = ((false == isMatchSecond) || secondMatchers.get(i).match(second)) // 匹配秒(非秒匹配模式下始终返回true)
|
||||
&& minuteMatchers.get(i).match(minute)// 匹配分
|
||||
&& hourMatchers.get(i).match(hour)// 匹配时
|
||||
&& isMatchDayOfMonth(dayOfMonthMatchers.get(i), dayOfMonth, month, calendar.isLeapYear(year))// 匹配日
|
||||
&& monthMatchers.get(i).match(month) // 匹配月
|
||||
&& dayOfWeekMatchers.get(i).match(dayOfWeek)// 匹配周
|
||||
&& isMatch(yearMatchers, i, year);// 匹配年
|
||||
if (eval) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return this.matcherTable.match(second, minute, hour, dayOfMonth, month, dayOfWeek, year);
|
||||
}
|
||||
// --------------------------------------------------------------------------------------- match end
|
||||
|
||||
@ -181,114 +129,4 @@ public class CronPattern {
|
||||
public String toString() {
|
||||
return this.pattern;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------------- Private method start
|
||||
/**
|
||||
* 是否匹配日(指定月份的第几天)
|
||||
*
|
||||
* @param matcher {@link ValueMatcher}
|
||||
* @param dayOfMonth 日
|
||||
* @param month 月
|
||||
* @param isLeapYear 是否闰年
|
||||
* @return 是否匹配
|
||||
*/
|
||||
private static boolean isMatchDayOfMonth(ValueMatcher matcher, int dayOfMonth, int month, boolean isLeapYear) {
|
||||
return ((matcher instanceof DayOfMonthValueMatcher) //
|
||||
? ((DayOfMonthValueMatcher) matcher).match(dayOfMonth, month, isLeapYear) //
|
||||
: matcher.match(dayOfMonth));
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否匹配指定的日期时间位置
|
||||
*
|
||||
* @param matchers 匹配器列表
|
||||
* @param index 位置
|
||||
* @param value 被匹配的值
|
||||
* @return 是否匹配
|
||||
* @since 4.0.2
|
||||
*/
|
||||
private static boolean isMatch(List<ValueMatcher> matchers, int index, int value) {
|
||||
return (matchers.size() <= index) || matchers.get(index).match(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析复合任务表达式
|
||||
*
|
||||
* @param groupPattern 复合表达式
|
||||
*/
|
||||
private void parseGroupPattern(String groupPattern) {
|
||||
List<String> patternList = StrUtil.split(groupPattern, '|');
|
||||
for (String pattern : patternList) {
|
||||
parseSinglePattern(pattern);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析单一定时任务表达式
|
||||
*
|
||||
* @param pattern 表达式
|
||||
*/
|
||||
private void parseSinglePattern(String pattern) {
|
||||
final String[] parts = pattern.split("\\s");
|
||||
|
||||
int offset = 0;// 偏移量用于兼容Quartz表达式,当表达式有6或7项时,第一项为秒
|
||||
if (parts.length == 6 || parts.length == 7) {
|
||||
offset = 1;
|
||||
} else if (parts.length != 5) {
|
||||
throw new CronException("Pattern [{}] is invalid, it must be 5-7 parts!", pattern);
|
||||
}
|
||||
|
||||
// 秒
|
||||
if (1 == offset) {// 支持秒的表达式
|
||||
try {
|
||||
this.secondMatchers.add(ValueMatcherBuilder.build(parts[0], SECOND_VALUE_PARSER));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'second' field error!", pattern);
|
||||
}
|
||||
} else {// 不支持秒的表达式,则第一位按照表达式生成时间的秒数赋值,表示整分匹配
|
||||
this.secondMatchers.add(ValueMatcherBuilder.build(String.valueOf(DateUtil.date().second()), SECOND_VALUE_PARSER));
|
||||
}
|
||||
// 分
|
||||
try {
|
||||
this.minuteMatchers.add(ValueMatcherBuilder.build(parts[offset], MINUTE_VALUE_PARSER));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'minute' field error!", pattern);
|
||||
}
|
||||
// 小时
|
||||
try {
|
||||
this.hourMatchers.add(ValueMatcherBuilder.build(parts[1 + offset], HOUR_VALUE_PARSER));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'hour' field error!", pattern);
|
||||
}
|
||||
// 每月第几天
|
||||
try {
|
||||
this.dayOfMonthMatchers.add(ValueMatcherBuilder.build(parts[2 + offset], DAY_OF_MONTH_VALUE_PARSER));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'day of month' field error!", pattern);
|
||||
}
|
||||
// 月
|
||||
try {
|
||||
this.monthMatchers.add(ValueMatcherBuilder.build(parts[3 + offset], MONTH_VALUE_PARSER));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'month' field error!", pattern);
|
||||
}
|
||||
// 星期几
|
||||
try {
|
||||
this.dayOfWeekMatchers.add(ValueMatcherBuilder.build(parts[4 + offset], DAY_OF_WEEK_VALUE_PARSER));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'day of week' field error!", pattern);
|
||||
}
|
||||
// 年
|
||||
if (parts.length == 7) {// 支持年的表达式
|
||||
try {
|
||||
this.yearMatchers.add(ValueMatcherBuilder.build(parts[6], YEAR_VALUE_PARSER));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'year' field error!", pattern);
|
||||
}
|
||||
} else {// 不支持年的表达式,全部匹配
|
||||
this.yearMatchers.add(new AlwaysTrueValueMatcher());
|
||||
}
|
||||
matcherSize++;
|
||||
}
|
||||
// -------------------------------------------------------------------------------------- Private method end
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package cn.hutool.cron.pattern.matcher;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
@ -7,28 +9,50 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* 将表达式中的数字值列表转换为Boolean数组,匹配时匹配相应数组位
|
||||
* @author Looly
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class BoolArrayValueMatcher implements ValueMatcher{
|
||||
|
||||
public class BoolArrayValueMatcher implements ValueMatcher {
|
||||
|
||||
/**
|
||||
* 用户定义此字段的最小值
|
||||
*/
|
||||
private final int minValue;
|
||||
private final boolean[] bValues;
|
||||
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param intValueList 匹配值列表
|
||||
*/
|
||||
public BoolArrayValueMatcher(List<Integer> intValueList) {
|
||||
Assert.isTrue(CollUtil.isNotEmpty(intValueList), "Values must be not empty!");
|
||||
bValues = new boolean[Collections.max(intValueList) + 1];
|
||||
int min = Integer.MAX_VALUE;
|
||||
for (Integer value : intValueList) {
|
||||
min = Math.min(min, value);
|
||||
bValues[value] = true;
|
||||
}
|
||||
this.minValue = min;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(Integer value) {
|
||||
if(null == value || value >= bValues.length){
|
||||
if (null == value || value >= bValues.length) {
|
||||
return false;
|
||||
}
|
||||
return bValues[value];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取表达式定义的最小值
|
||||
*
|
||||
* @return 最小值
|
||||
*/
|
||||
public int getMinValue() {
|
||||
return this.minValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return StrUtil.format("Matcher:{}", new Object[]{this.bValues});
|
||||
|
@ -0,0 +1,119 @@
|
||||
package cn.hutool.cron.pattern.matcher;
|
||||
|
||||
import java.time.Year;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 时间匹配表,用于存放定时任务表达式解析后的结构信息
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public class MatcherTable {
|
||||
|
||||
/**
|
||||
* 匹配器个数,取决于复合任务表达式中的单一表达式个数
|
||||
*/
|
||||
public int matcherSize;
|
||||
/**
|
||||
* 秒字段匹配列表
|
||||
*/
|
||||
public final List<ValueMatcher> secondMatchers;
|
||||
/**
|
||||
* 分字段匹配列表
|
||||
*/
|
||||
public final List<ValueMatcher> minuteMatchers;
|
||||
/**
|
||||
* 时字段匹配列表
|
||||
*/
|
||||
public final List<ValueMatcher> hourMatchers;
|
||||
/**
|
||||
* 每月几号字段匹配列表
|
||||
*/
|
||||
public final List<ValueMatcher> dayOfMonthMatchers;
|
||||
/**
|
||||
* 月字段匹配列表
|
||||
*/
|
||||
public final List<ValueMatcher> monthMatchers;
|
||||
/**
|
||||
* 星期字段匹配列表
|
||||
*/
|
||||
public final List<ValueMatcher> dayOfWeekMatchers;
|
||||
/**
|
||||
* 年字段匹配列表
|
||||
*/
|
||||
public final List<ValueMatcher> yearMatchers;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param size 表达式个数,用于表示复合表达式中单个表达式个数
|
||||
*/
|
||||
public MatcherTable(int size) {
|
||||
matcherSize = size;
|
||||
secondMatchers = new ArrayList<>(size);
|
||||
minuteMatchers = new ArrayList<>(size);
|
||||
hourMatchers = new ArrayList<>(size);
|
||||
dayOfMonthMatchers = new ArrayList<>(size);
|
||||
monthMatchers = new ArrayList<>(size);
|
||||
dayOfWeekMatchers = new ArrayList<>(size);
|
||||
yearMatchers = new ArrayList<>(size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给定时间是否匹配定时任务表达式
|
||||
*
|
||||
* @param second 秒数,-1表示不匹配此项
|
||||
* @param minute 分钟
|
||||
* @param hour 小时
|
||||
* @param dayOfMonth 天
|
||||
* @param month 月
|
||||
* @param dayOfWeek 周几
|
||||
* @param year 年
|
||||
* @return 如果匹配返回 {@code true}, 否则返回 {@code false}
|
||||
*/
|
||||
public boolean match(int second, int minute, int hour, int dayOfMonth, int month, int dayOfWeek, int year) {
|
||||
for (int i = 0; i < matcherSize; i++) {
|
||||
boolean eval = ((second < 0) || secondMatchers.get(i).match(second)) // 匹配秒(非秒匹配模式下始终返回true)
|
||||
&& minuteMatchers.get(i).match(minute)// 匹配分
|
||||
&& hourMatchers.get(i).match(hour)// 匹配时
|
||||
&& isMatchDayOfMonth(dayOfMonthMatchers.get(i), dayOfMonth, month, Year.isLeap(year))// 匹配日
|
||||
&& monthMatchers.get(i).match(month) // 匹配月
|
||||
&& dayOfWeekMatchers.get(i).match(dayOfWeek)// 匹配周
|
||||
&& isMatch(yearMatchers, i, year);// 匹配年
|
||||
if (eval) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否匹配日(指定月份的第几天)
|
||||
*
|
||||
* @param matcher {@link ValueMatcher}
|
||||
* @param dayOfMonth 日
|
||||
* @param month 月
|
||||
* @param isLeapYear 是否闰年
|
||||
* @return 是否匹配
|
||||
*/
|
||||
private static boolean isMatchDayOfMonth(ValueMatcher matcher, int dayOfMonth, int month, boolean isLeapYear) {
|
||||
return ((matcher instanceof DayOfMonthValueMatcher) //
|
||||
? ((DayOfMonthValueMatcher) matcher).match(dayOfMonth, month, isLeapYear) //
|
||||
: matcher.match(dayOfMonth));
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否匹配指定的日期时间位置
|
||||
*
|
||||
* @param matchers 匹配器列表
|
||||
* @param index 位置
|
||||
* @param value 被匹配的值
|
||||
* @return 是否匹配
|
||||
* @since 4.0.2
|
||||
*/
|
||||
private static boolean isMatch(List<ValueMatcher> matchers, int index, int value) {
|
||||
return (matchers.size() <= index) || matchers.get(index).match(value);
|
||||
}
|
||||
}
|
@ -1,53 +1,111 @@
|
||||
package cn.hutool.cron.pattern.matcher;
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.NumberUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.cron.CronException;
|
||||
import cn.hutool.cron.pattern.parser.DayOfMonthValueParser;
|
||||
import cn.hutool.cron.pattern.parser.ValueParser;
|
||||
import cn.hutool.cron.pattern.parser.YearValueParser;
|
||||
import cn.hutool.cron.pattern.matcher.AlwaysTrueValueMatcher;
|
||||
import cn.hutool.cron.pattern.matcher.BoolArrayValueMatcher;
|
||||
import cn.hutool.cron.pattern.matcher.ValueMatcher;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* {@link ValueMatcher} 构建器,用于构建表达式中每一项的匹配器
|
||||
* @author Looly
|
||||
* 简易值转换器。将给定String值转为int,并限定最大值和最小值<br>
|
||||
* 此类同时识别{@code L} 为最大值。
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class ValueMatcherBuilder {
|
||||
|
||||
public abstract class AbsValueParser implements ValueParser {
|
||||
|
||||
/**
|
||||
* 最小值(包括)
|
||||
*/
|
||||
protected int min;
|
||||
/**
|
||||
* 最大值(包括)
|
||||
*/
|
||||
protected int max;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param min 最小值(包括)
|
||||
* @param max 最大值(包括)
|
||||
*/
|
||||
public AbsValueParser(int min, int max) {
|
||||
if (min > max) {
|
||||
this.min = max;
|
||||
this.max = min;
|
||||
} else {
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int parse(String value) throws CronException {
|
||||
if ("L".equalsIgnoreCase(value)) {
|
||||
// L表示最大值
|
||||
return max;
|
||||
}
|
||||
|
||||
int i;
|
||||
try {
|
||||
i = Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new CronException(e, "Invalid integer value: '{}'", value);
|
||||
}
|
||||
if (i < min || i > max) {
|
||||
throw new CronException("Value {} out of range: [{} , {}]", i, min, max);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMin() {
|
||||
return this.min;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMax() {
|
||||
return this.max;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理定时任务表达式每个时间字段<br>
|
||||
* 多个时间使用逗号分隔
|
||||
*
|
||||
*
|
||||
* @param value 某个时间字段
|
||||
* @param parser 针对这个时间字段的解析器
|
||||
* @return List
|
||||
*/
|
||||
public static ValueMatcher build(String value, ValueParser parser) {
|
||||
@Override
|
||||
public ValueMatcher parseAsValueMatcher(String value) {
|
||||
if (isMatchAllStr(value)) {
|
||||
//兼容Quartz的"?"表达式,不会出现互斥情况,与"*"作用相同
|
||||
return new AlwaysTrueValueMatcher();
|
||||
}
|
||||
|
||||
List<Integer> values = parseArray(value, parser);
|
||||
List<Integer> values = parseArray(value);
|
||||
if (values.size() == 0) {
|
||||
throw new CronException("Invalid field: [{}]", value);
|
||||
}
|
||||
|
||||
if (parser instanceof DayOfMonthValueParser) {
|
||||
//考虑每月的天数不同,且存在闰年情况,日匹配单独使用
|
||||
return new DayOfMonthValueMatcher(values);
|
||||
}else if(parser instanceof YearValueParser){
|
||||
//考虑年数字太大,不适合boolean数组,单独使用列表遍历匹配
|
||||
return new YearValueMatcher(values);
|
||||
}else {
|
||||
return new BoolArrayValueMatcher(values);
|
||||
}
|
||||
return buildValueMatcher(values);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据解析的数字值列表构建{@link ValueMatcher}<br>
|
||||
* 默认为{@link BoolArrayValueMatcher},如果有特殊实现,子类须重写此方法
|
||||
*
|
||||
* @param values 数字值列表
|
||||
* @return {@link ValueMatcher}
|
||||
*/
|
||||
protected ValueMatcher buildValueMatcher(List<Integer> values){
|
||||
return new BoolArrayValueMatcher(values);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理数组形式表达式<br>
|
||||
* 处理的形式包括:
|
||||
@ -56,15 +114,14 @@ public class ValueMatcherBuilder {
|
||||
* <li><strong>a,b,c,d</strong></li>
|
||||
* </ul>
|
||||
* @param value 子表达式值
|
||||
* @param parser 针对这个字段的解析器
|
||||
* @return 值列表
|
||||
*/
|
||||
private static List<Integer> parseArray(String value, ValueParser parser){
|
||||
private List<Integer> parseArray(String value){
|
||||
final List<Integer> values = new ArrayList<>();
|
||||
|
||||
|
||||
final List<String> parts = StrUtil.split(value, StrUtil.C_COMMA);
|
||||
for (String part : parts) {
|
||||
CollUtil.addAllIfNotContains(values, parseStep(part, parser));
|
||||
CollUtil.addAllIfNotContains(values, parseStep(part));
|
||||
}
|
||||
return values;
|
||||
}
|
||||
@ -77,24 +134,23 @@ public class ValueMatcherBuilder {
|
||||
* <li><strong>a/b</strong> 或 <strong>*/b</strong></li>
|
||||
* <li><strong>a-b/2</strong></li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* @param value 表达式值
|
||||
* @param parser 针对这个时间字段的解析器
|
||||
* @return List
|
||||
*/
|
||||
private static List<Integer> parseStep(String value, ValueParser parser) {
|
||||
private List<Integer> parseStep(String value) {
|
||||
final List<String> parts = StrUtil.split(value, StrUtil.C_SLASH);
|
||||
int size = parts.size();
|
||||
|
||||
|
||||
List<Integer> results;
|
||||
if (size == 1) {// 普通形式
|
||||
results = parseRange(value, -1, parser);
|
||||
results = parseRange(value, -1);
|
||||
} else if (size == 2) {// 间隔形式
|
||||
final int step = parser.parse(parts.get(1));
|
||||
final int step = parse(parts.get(1));
|
||||
if (step < 1) {
|
||||
throw new CronException("Non positive divisor for field: [{}]", value);
|
||||
}
|
||||
results = parseRange(parts.get(0), step, parser);
|
||||
results = parseRange(parts.get(0), step);
|
||||
} else {
|
||||
throw new CronException("Invalid syntax of field: [{}]", value);
|
||||
}
|
||||
@ -110,21 +166,20 @@ public class ValueMatcherBuilder {
|
||||
* <li>8-3</li>
|
||||
* <li>3-3</li>
|
||||
* </ul>
|
||||
*
|
||||
*
|
||||
* @param value 范围表达式
|
||||
* @param step 步进
|
||||
* @param parser 针对这个时间字段的解析器
|
||||
* @return List
|
||||
*/
|
||||
private static List<Integer> parseRange(String value, int step, ValueParser parser) {
|
||||
private List<Integer> parseRange(String value, int step) {
|
||||
final List<Integer> results = new ArrayList<>();
|
||||
|
||||
|
||||
// 全部匹配形式
|
||||
if (value.length() <= 2) {
|
||||
//根据步进的第一个数字确定起始时间,类似于 12/3则从12(秒、分等)开始
|
||||
int minValue = parser.getMin();
|
||||
int minValue = getMin();
|
||||
if(false == isMatchAllStr(value)) {
|
||||
minValue = Math.max(minValue, parser.parse(value));
|
||||
minValue = Math.max(minValue, parse(value));
|
||||
}else {
|
||||
//在全匹配模式下,如果步进不存在,表示步进为1
|
||||
if(step < 1) {
|
||||
@ -132,7 +187,7 @@ public class ValueMatcherBuilder {
|
||||
}
|
||||
}
|
||||
if(step > 0) {
|
||||
final int maxValue = parser.getMax();
|
||||
final int maxValue = getMax();
|
||||
if(minValue > maxValue) {
|
||||
throw new CronException("Invalid value {} > {}", minValue, maxValue);
|
||||
}
|
||||
@ -151,15 +206,15 @@ public class ValueMatcherBuilder {
|
||||
List<String> parts = StrUtil.split(value, '-');
|
||||
int size = parts.size();
|
||||
if (size == 1) {// 普通值
|
||||
final int v1 = parser.parse(value);
|
||||
final int v1 = parse(value);
|
||||
if(step > 0) {//类似 20/2的形式
|
||||
NumberUtil.appendRange(v1, parser.getMax(), step, results);
|
||||
NumberUtil.appendRange(v1, getMax(), step, results);
|
||||
}else {
|
||||
results.add(v1);
|
||||
}
|
||||
} else if (size == 2) {// range值
|
||||
final int v1 = parser.parse(parts.get(0));
|
||||
final int v2 = parser.parse(parts.get(1));
|
||||
final int v1 = parse(parts.get(0));
|
||||
final int v2 = parse(parts.get(1));
|
||||
if(step < 1) {
|
||||
//在range模式下,如果步进不存在,表示步进为1
|
||||
step = 1;
|
||||
@ -167,21 +222,21 @@ public class ValueMatcherBuilder {
|
||||
if (v1 < v2) {// 正常范围,例如:2-5
|
||||
NumberUtil.appendRange(v1, v2, step, results);
|
||||
} else if (v1 > v2) {// 逆向范围,反选模式,例如:5-2
|
||||
NumberUtil.appendRange(v1, parser.getMax(), step, results);
|
||||
NumberUtil.appendRange(parser.getMin(), v2, step, results);
|
||||
NumberUtil.appendRange(v1, getMax(), step, results);
|
||||
NumberUtil.appendRange(getMin(), v2, step, results);
|
||||
} else {// v1 == v2,此时与单值模式一致
|
||||
NumberUtil.appendRange(v1, parser.getMax(), step, results);
|
||||
NumberUtil.appendRange(v1, getMax(), step, results);
|
||||
}
|
||||
} else {
|
||||
throw new CronException("Invalid syntax of field: [{}]", value);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 是否为全匹配符<br>
|
||||
* 全匹配符指 * 或者 ?
|
||||
*
|
||||
*
|
||||
* @param value 被检查的值
|
||||
* @return 是否为全匹配符
|
||||
* @since 4.1.18
|
@ -0,0 +1,116 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.cron.CronException;
|
||||
import cn.hutool.cron.pattern.matcher.AlwaysTrueValueMatcher;
|
||||
import cn.hutool.cron.pattern.matcher.MatcherTable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 定时任务表达式解析器,用于将表达式字符串解析为{@link MatcherTable}
|
||||
*
|
||||
* @author looly
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public class CronPatternParser {
|
||||
|
||||
private static final ValueParser SECOND_VALUE_PARSER = new SecondValueParser();
|
||||
private static final ValueParser MINUTE_VALUE_PARSER = new MinuteValueParser();
|
||||
private static final ValueParser HOUR_VALUE_PARSER = new HourValueParser();
|
||||
private static final ValueParser DAY_OF_MONTH_VALUE_PARSER = new DayOfMonthValueParser();
|
||||
private static final ValueParser MONTH_VALUE_PARSER = new MonthValueParser();
|
||||
private static final ValueParser DAY_OF_WEEK_VALUE_PARSER = new DayOfWeekValueParser();
|
||||
private static final ValueParser YEAR_VALUE_PARSER = new YearValueParser();
|
||||
|
||||
/**
|
||||
* 创建表达式解析器
|
||||
*
|
||||
* @return CronPatternParser
|
||||
*/
|
||||
public static CronPatternParser create() {
|
||||
return new CronPatternParser();
|
||||
}
|
||||
|
||||
private MatcherTable matcherTable;
|
||||
|
||||
/**
|
||||
* 解析表达式到匹配表中
|
||||
*
|
||||
* @param cronPattern 复合表达式
|
||||
* @return {@link MatcherTable}
|
||||
*/
|
||||
public MatcherTable parse(String cronPattern) {
|
||||
parseGroupPattern(cronPattern);
|
||||
return this.matcherTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析复合任务表达式,格式为:
|
||||
* <pre>
|
||||
* cronA | cronB | ...
|
||||
* </pre>
|
||||
*
|
||||
* @param groupPattern 复合表达式
|
||||
*/
|
||||
private void parseGroupPattern(String groupPattern) {
|
||||
final List<String> patternList = StrUtil.split(groupPattern, '|');
|
||||
matcherTable = new MatcherTable(patternList.size());
|
||||
for (String pattern : patternList) {
|
||||
parseSinglePattern(pattern);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析单一定时任务表达式
|
||||
*
|
||||
* @param pattern 表达式
|
||||
*/
|
||||
private void parseSinglePattern(String pattern) {
|
||||
final String[] parts = pattern.split("\\s");
|
||||
|
||||
int offset = 0;// 偏移量用于兼容Quartz表达式,当表达式有6或7项时,第一项为秒
|
||||
if (parts.length == 6 || parts.length == 7) {
|
||||
offset = 1;
|
||||
} else if (parts.length != 5) {
|
||||
throw new CronException("Pattern [{}] is invalid, it must be 5-7 parts!", pattern);
|
||||
}
|
||||
|
||||
// 秒,如果不支持秒的表达式,则第一位按照表达式生成时间的秒数赋值,表示整分匹配
|
||||
final String secondPart = (1 == offset) ? parts[0] : String.valueOf(DateUtil.date().second());
|
||||
parseToTable(SECOND_VALUE_PARSER, secondPart);
|
||||
|
||||
// 分
|
||||
parseToTable(MINUTE_VALUE_PARSER, parts[offset]);
|
||||
|
||||
// 时
|
||||
parseToTable(HOUR_VALUE_PARSER, parts[1 + offset]);
|
||||
|
||||
// 天
|
||||
parseToTable(DAY_OF_MONTH_VALUE_PARSER, parts[2 + offset]);
|
||||
|
||||
// 月
|
||||
parseToTable(MONTH_VALUE_PARSER, parts[3 + offset]);
|
||||
|
||||
// 周
|
||||
parseToTable(DAY_OF_WEEK_VALUE_PARSER, parts[4 + offset]);
|
||||
|
||||
// 年
|
||||
if (parts.length == 7) {// 支持年的表达式
|
||||
parseToTable(YEAR_VALUE_PARSER, parts[6]);
|
||||
} else {// 不支持年的表达式,全部匹配
|
||||
matcherTable.yearMatchers.add(new AlwaysTrueValueMatcher());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 将表达式解析后加入到{@link #matcherTable}中
|
||||
*
|
||||
* @param valueParser 表达式解析器
|
||||
* @param patternPart 表达式部分
|
||||
*/
|
||||
private void parseToTable(ValueParser valueParser, String patternPart) {
|
||||
valueParser.parseTo(this.matcherTable, patternPart);
|
||||
}
|
||||
}
|
@ -1,16 +1,24 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.cron.CronException;
|
||||
import cn.hutool.cron.pattern.matcher.DayOfMonthValueMatcher;
|
||||
import cn.hutool.cron.pattern.matcher.MatcherTable;
|
||||
import cn.hutool.cron.pattern.matcher.ValueMatcher;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 每月的几号值处理<br>
|
||||
* 每月最多31天,32和“L”都表示最后一天
|
||||
*
|
||||
*
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class DayOfMonthValueParser extends SimpleValueParser {
|
||||
public class DayOfMonthValueParser extends AbsValueParser {
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public DayOfMonthValueParser() {
|
||||
super(1, 31);
|
||||
}
|
||||
@ -23,4 +31,19 @@ public class DayOfMonthValueParser extends SimpleValueParser {
|
||||
return super.parse(value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseTo(MatcherTable matcherTable, String pattern) {
|
||||
try {
|
||||
matcherTable.dayOfMonthMatchers.add(parseAsValueMatcher(pattern));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'day of month' field error!", pattern);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ValueMatcher buildValueMatcher(List<Integer> values) {
|
||||
//考虑每月的天数不同,且存在闰年情况,日匹配单独使用
|
||||
return new DayOfMonthValueMatcher(values);
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +1,26 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.cron.CronException;
|
||||
import cn.hutool.cron.pattern.matcher.MatcherTable;
|
||||
|
||||
/**
|
||||
* 星期值处理<br>
|
||||
* 1表示星期一,2表示星期二,依次类推,0和7都可以表示星期日
|
||||
*
|
||||
* @author Looly
|
||||
* 1表示星期一,2表示星期二,依次类推,0和7都可以表示星期日<br>
|
||||
* {@code L}表示周六
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class DayOfWeekValueParser extends SimpleValueParser {
|
||||
|
||||
/** Weeks aliases. */
|
||||
private static final String[] ALIASES = { "sun", "mon", "tue", "wed", "thu", "fri", "sat" };
|
||||
public class DayOfWeekValueParser extends AbsValueParser {
|
||||
|
||||
/**
|
||||
* Weeks aliases.
|
||||
*/
|
||||
private static final String[] ALIASES = {"sun", "mon", "tue", "wed", "thu", "fri", "sat"};
|
||||
|
||||
public DayOfWeekValueParser() {
|
||||
super(0, 7);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 对于星期提供转换<br>
|
||||
* 1表示星期一,2表示星期二,依次类推,0和7都可以表示星期日
|
||||
@ -31,18 +34,28 @@ public class DayOfWeekValueParser extends SimpleValueParser {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseTo(MatcherTable matcherTable, String pattern) {
|
||||
try {
|
||||
matcherTable.dayOfWeekMatchers.add(parseAsValueMatcher(pattern));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'day of week' field error!", pattern);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析别名
|
||||
*
|
||||
* @param value 别名值
|
||||
* @return 月份int值
|
||||
* @throws CronException 无效别名抛出此异常
|
||||
*/
|
||||
private int parseAlias(String value) throws CronException {
|
||||
if("L".equalsIgnoreCase(value)){
|
||||
if ("L".equalsIgnoreCase(value)) {
|
||||
//最后一天为星期六
|
||||
return ALIASES.length - 1;
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < ALIASES.length; i++) {
|
||||
if (ALIASES[i].equalsIgnoreCase(value)) {
|
||||
return i;
|
||||
|
@ -1,14 +1,26 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.cron.CronException;
|
||||
import cn.hutool.cron.pattern.matcher.MatcherTable;
|
||||
|
||||
/**
|
||||
* 小时值处理
|
||||
* @author Looly
|
||||
* 小时值处理<br>
|
||||
* 小时被限定在0-23
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class HourValueParser extends SimpleValueParser{
|
||||
|
||||
public class HourValueParser extends AbsValueParser {
|
||||
|
||||
public HourValueParser() {
|
||||
super(0, 23);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseTo(MatcherTable matcherTable, String pattern) {
|
||||
try {
|
||||
matcherTable.hourMatchers.add(parseAsValueMatcher(pattern));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'hour' field error!", pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,14 +1,29 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.cron.CronException;
|
||||
import cn.hutool.cron.pattern.matcher.MatcherTable;
|
||||
|
||||
/**
|
||||
* 分钟值处理
|
||||
* @author Looly
|
||||
* 分钟值处理<br>
|
||||
* 限定于0-59
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class MinuteValueParser extends SimpleValueParser{
|
||||
|
||||
public class MinuteValueParser extends AbsValueParser {
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public MinuteValueParser() {
|
||||
super(0, 59);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseTo(MatcherTable matcherTable, String pattern) {
|
||||
try {
|
||||
matcherTable.minuteMatchers.add(parseAsValueMatcher(pattern));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'minute' field error!", pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,25 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.cron.CronException;
|
||||
import cn.hutool.cron.pattern.matcher.MatcherTable;
|
||||
|
||||
/**
|
||||
* 月份值处理
|
||||
*
|
||||
* @author Looly
|
||||
* 月份值处理<br>
|
||||
* 限定于1-12,1表示一月,支持别名(忽略大小写),如一月是{@code jan}
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class MonthValueParser extends SimpleValueParser {
|
||||
public class MonthValueParser extends AbsValueParser {
|
||||
|
||||
/** Months aliases. */
|
||||
private static final String[] ALIASES = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" };
|
||||
/**
|
||||
* Months aliases.
|
||||
*/
|
||||
private static final String[] ALIASES = {"jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"};
|
||||
|
||||
public MonthValueParser() {
|
||||
super(1, 12);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int parse(String value) throws CronException {
|
||||
try {
|
||||
@ -26,8 +29,18 @@ public class MonthValueParser extends SimpleValueParser {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseTo(MatcherTable matcherTable, String pattern) {
|
||||
try {
|
||||
matcherTable.monthMatchers.add(parseAsValueMatcher(pattern));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'month' field error!", pattern);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析别名
|
||||
*
|
||||
* @param value 别名值
|
||||
* @return 月份int值
|
||||
* @throws CronException 无效月别名抛出此异常
|
||||
|
@ -1,9 +1,22 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.cron.CronException;
|
||||
import cn.hutool.cron.pattern.matcher.MatcherTable;
|
||||
|
||||
/**
|
||||
* 秒值处理
|
||||
* @author Looly
|
||||
* 秒值处理<br>
|
||||
* 限定于0-59
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class SecondValueParser extends MinuteValueParser{
|
||||
public class SecondValueParser extends MinuteValueParser {
|
||||
|
||||
@Override
|
||||
public void parseTo(MatcherTable matcherTable, String pattern) {
|
||||
try {
|
||||
matcherTable.secondMatchers.add(parseAsValueMatcher(pattern));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'second' field error!", pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,61 +0,0 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.cron.CronException;
|
||||
|
||||
/**
|
||||
* 简易值转换器。将给定String值转为int
|
||||
* @author Looly
|
||||
*
|
||||
*/
|
||||
public class SimpleValueParser implements ValueParser {
|
||||
|
||||
/** 最小值(包括) */
|
||||
protected int min;
|
||||
/** 最大值(包括) */
|
||||
protected int max;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*
|
||||
* @param min 最小值(包括)
|
||||
* @param max 最大值(包括)
|
||||
*/
|
||||
public SimpleValueParser(int min, int max) {
|
||||
if(min > max){
|
||||
this.min = max;
|
||||
this.max = min;
|
||||
}else{
|
||||
this.min = min;
|
||||
this.max = max;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int parse(String value) throws CronException {
|
||||
if("L".equalsIgnoreCase(value)){
|
||||
// L表示最大值
|
||||
return max;
|
||||
}
|
||||
|
||||
int i;
|
||||
try {
|
||||
i = Integer.parseInt(value);
|
||||
} catch (NumberFormatException e) {
|
||||
throw new CronException(e, "Invalid integer value: '{}'", value);
|
||||
}
|
||||
if (i < min || i > max) {
|
||||
throw new CronException("Value {} out of range: [{} , {}]", i, min, max);
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMin() {
|
||||
return this.min;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMax() {
|
||||
return this.max;
|
||||
}
|
||||
}
|
@ -1,5 +1,8 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.cron.pattern.matcher.MatcherTable;
|
||||
import cn.hutool.cron.pattern.matcher.ValueMatcher;
|
||||
|
||||
/**
|
||||
* 值处理接口<br>
|
||||
* 值处理用于限定表达式中相应位置的值范围,并转换表达式值为int值
|
||||
@ -8,6 +11,28 @@ package cn.hutool.cron.pattern.parser;
|
||||
*/
|
||||
public interface ValueParser {
|
||||
|
||||
/**
|
||||
* 解析表达式后,加入到{@link MatcherTable}的对应列表中
|
||||
*
|
||||
* @param matcherTable {@link MatcherTable}
|
||||
* @param pattern 对应时间部分的表达式
|
||||
*/
|
||||
void parseTo(MatcherTable matcherTable, String pattern);
|
||||
|
||||
/**
|
||||
* 解析表达式对应部分为{@link ValueMatcher},支持的表达式包括:
|
||||
* <ul>
|
||||
* <li>单值或通配符形式,如 <strong>a</strong> 或 <strong>*</strong></li>
|
||||
* <li>数组形式,如 <strong>1,2,3</strong></li>
|
||||
* <li>间隔形式,如 <strong>a/b</strong> 或 <strong>*/b</strong></li>
|
||||
* <li>范围形式,如 <strong>3-8</strong></li>
|
||||
* </ul>
|
||||
*
|
||||
* @param pattern 对应时间部分的表达式
|
||||
* @return {@link ValueMatcher}
|
||||
*/
|
||||
ValueMatcher parseAsValueMatcher(String pattern);
|
||||
|
||||
/**
|
||||
* 处理String值并转为int<br>
|
||||
* 转换包括:
|
||||
@ -15,7 +40,7 @@ public interface ValueParser {
|
||||
* <li>数字字符串转为数字</li>
|
||||
* <li>别名转为对应的数字(如月份和星期)</li>
|
||||
* </ol>
|
||||
*
|
||||
*
|
||||
* @param value String值
|
||||
* @return int
|
||||
*/
|
||||
@ -23,14 +48,14 @@ public interface ValueParser {
|
||||
|
||||
/**
|
||||
* 返回最小值
|
||||
*
|
||||
*
|
||||
* @return 最小值
|
||||
*/
|
||||
int getMin();
|
||||
|
||||
/**
|
||||
* 返回最大值
|
||||
*
|
||||
*
|
||||
* @return 最大值
|
||||
*/
|
||||
int getMax();
|
||||
|
@ -1,14 +1,36 @@
|
||||
package cn.hutool.cron.pattern.parser;
|
||||
|
||||
import cn.hutool.cron.CronException;
|
||||
import cn.hutool.cron.pattern.matcher.MatcherTable;
|
||||
import cn.hutool.cron.pattern.matcher.ValueMatcher;
|
||||
import cn.hutool.cron.pattern.matcher.YearValueMatcher;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 年值处理
|
||||
* @author Looly
|
||||
* 年值处理<br>
|
||||
* 年的限定在1970-2099年
|
||||
*
|
||||
* @author Looly
|
||||
*/
|
||||
public class YearValueParser extends SimpleValueParser{
|
||||
|
||||
public class YearValueParser extends AbsValueParser {
|
||||
|
||||
public YearValueParser() {
|
||||
super(1970, 2099);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void parseTo(MatcherTable matcherTable, String pattern) {
|
||||
try {
|
||||
matcherTable.yearMatchers.add(parseAsValueMatcher(pattern));
|
||||
} catch (Exception e) {
|
||||
throw new CronException(e, "Invalid pattern [{}], parsing 'year' field error!", pattern);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ValueMatcher buildValueMatcher(List<Integer> values) {
|
||||
//考虑年数字太大,不适合boolean数组,单独使用列表遍历匹配
|
||||
return new YearValueMatcher(values);
|
||||
}
|
||||
}
|
||||
|
@ -163,7 +163,8 @@ public class HMac implements Serializable {
|
||||
* @return 摘要
|
||||
*/
|
||||
public String digestBase64(String data, Charset charset, boolean isUrlSafe) {
|
||||
return Base64.encodeStr(digest(data, charset), false, isUrlSafe);
|
||||
final byte[] digest = digest(data, charset);
|
||||
return isUrlSafe ? Base64.encodeUrlSafe(digest) : Base64.encode(digest);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -23,7 +23,7 @@ public enum GlobalHeaders {
|
||||
/**
|
||||
* 存储头信息
|
||||
*/
|
||||
Map<String, List<String>> headers = new HashMap<>();
|
||||
final Map<String, List<String>> headers = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 构造
|
||||
@ -43,6 +43,10 @@ public enum GlobalHeaders {
|
||||
// https://stackoverflow.com/questions/9096987/how-to-overwrite-http-header-host-in-a-httpurlconnection/9098440
|
||||
System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
|
||||
|
||||
//解决server certificate change is restricted during renegotiation问题
|
||||
System.setProperty("jdk.tls.allowUnsafeServerCertChange", "true");
|
||||
System.setProperty("sun.security.ssl.allowUnsafeRenegotiation", "true");
|
||||
|
||||
if (isReset) {
|
||||
this.headers.clear();
|
||||
}
|
||||
|
@ -345,7 +345,7 @@ public class ExcelUtil {
|
||||
* @return {@link BigExcelWriter}
|
||||
* @since 4.1.13
|
||||
*/
|
||||
public static ExcelWriter getBigWriter() {
|
||||
public static BigExcelWriter getBigWriter() {
|
||||
try {
|
||||
return new BigExcelWriter();
|
||||
} catch (NoClassDefFoundError e) {
|
||||
@ -362,7 +362,7 @@ public class ExcelUtil {
|
||||
* @return {@link BigExcelWriter}
|
||||
* @since 4.1.13
|
||||
*/
|
||||
public static ExcelWriter getBigWriter(int rowAccessWindowSize) {
|
||||
public static BigExcelWriter getBigWriter(int rowAccessWindowSize) {
|
||||
try {
|
||||
return new BigExcelWriter(rowAccessWindowSize);
|
||||
} catch (NoClassDefFoundError e) {
|
||||
|
@ -3,6 +3,7 @@ package cn.hutool.poi.excel.sax;
|
||||
import cn.hutool.core.io.IORuntimeException;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.poi.excel.sax.handler.RowHandler;
|
||||
import cn.hutool.poi.exceptions.POIException;
|
||||
@ -127,13 +128,9 @@ public class Excel07SaxReader implements ExcelSaxReader<Excel07SaxReader> {
|
||||
}
|
||||
|
||||
// 获取共享字符串表
|
||||
try {
|
||||
this.handler.sharedStrings = xssfReader.getSharedStringsTable();
|
||||
} catch (IOException e) {
|
||||
throw new IORuntimeException(e);
|
||||
} catch (InvalidFormatException e) {
|
||||
throw new POIException(e);
|
||||
}
|
||||
// POI-5.2.0开始返回值有所变更,导致实际使用时提示方法未找到,此处使用反射调用,解决不同版本返回值变更问题
|
||||
//this.handler.sharedStrings = xssfReader.getSharedStringsTable();
|
||||
this.handler.sharedStrings = ReflectUtil.invoke(xssfReader, "getSharedStringsTable");
|
||||
|
||||
return readSheets(xssfReader, idOrRidOrSheetName);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user