Merge remote-tracking branch 'origin/v5-dev' into v5-dev

This commit is contained in:
liuyulin 2022-03-22 14:55:58 +08:00
commit 97cc945a67
35 changed files with 1345 additions and 663 deletions

View File

@ -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)

View File

@ -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就是用322的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);
}
}

View 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就是用322的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;
}
}
}

View File

@ -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);
}
}
/**

View File

@ -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);
}
}

View File

@ -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) {

View File

@ -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);
}
/**

View File

@ -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
}

View File

@ -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 -&gt; '9'
* </pre>
*
* @param d 输入字符
* @return 转换后的字符
* @throws UtilException 无效字符
@ -242,6 +243,7 @@ public class PunyCode {
* ...
* '9' -&gt; 35
* </pre>
*
* @param c 输入字符
* @return 转换后的字符
* @throws UtilException 无效字符

View File

@ -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);
}
/**

View File

@ -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);
}

View File

@ -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);
}
/**
* 构造
*

View File

@ -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返回0x&lt;y返回-1x&gt;y返回1
*/
@Override

View File

@ -137,6 +137,7 @@ public class AntPathMatcher {
*
* @param cachePatterns 是否缓存表达式
* @see #getStringMatcher(String)
* @return this
*/
public AntPathMatcher setCachePatterns(boolean cachePatterns) {
this.cachePatterns = cachePatterns;

View File

@ -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

View File

@ -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);
}
}

View File

@ -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();
});
}
}

View File

@ -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>
* 间隔/ &gt; 区间- &gt; 列表,
* </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
}

View File

@ -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});

View File

@ -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);
}
}

View File

@ -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&#47;b</strong> <strong>*&#47;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

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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-121表示一月支持别名忽略大小写如一月是{@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 无效月别名抛出此异常

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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&#47;b</strong> <strong>*&#47;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();

View File

@ -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);
}
}

View File

@ -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);
}
/**

View File

@ -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();
}

View File

@ -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) {

View File

@ -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);
}