From 2f5a68c8a5a36f916ccd13f3bc8c3c320da3648e Mon Sep 17 00:00:00 2001 From: kingery Date: Sun, 20 Mar 2022 20:38:40 +0800 Subject: [PATCH 01/13] =?UTF-8?q?feat(LazyFunLoader):=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E9=9D=99=E6=80=81=E5=B7=A5=E5=8E=82=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/lang/loader/LazyFunLoader.java | 11 ++++++++ .../core/lang/loader/LazyFunLoaderTest.java | 28 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/loader/LazyFunLoader.java b/hutool-core/src/main/java/cn/hutool/core/lang/loader/LazyFunLoader.java index e50e80a76..3cc626a6f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/loader/LazyFunLoader.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/loader/LazyFunLoader.java @@ -24,6 +24,17 @@ public class LazyFunLoader extends LazyLoader { */ private Supplier supplier; + /** + * 静态工厂方法,提供语义性与编码便利性 + * @param supplier 用于生成对象的函数 + * @param 对象类型 + * @return 函数式懒加载加载器对象 + */ + public static LazyFunLoader onLoad(final Supplier supplier) { + Assert.notNull(supplier, "supplier"); + return new LazyFunLoader<>(supplier); + } + /** * 构造 * diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/loader/LazyFunLoaderTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/loader/LazyFunLoaderTest.java index 6de9cb529..0e35bf717 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/loader/LazyFunLoaderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/loader/LazyFunLoaderTest.java @@ -42,4 +42,32 @@ public class LazyFunLoaderTest { Assert.assertFalse(loader.isInitialize()); } + + @Test + public void testOnLoadStaticFactoryMethod1() { + + LazyFunLoader loader = LazyFunLoader.onLoad(BigObject::new); + + Assert.assertNotNull(loader.get()); + Assert.assertTrue(loader.isInitialize()); + + // 对于某些对象,在程序关闭时,需要进行销毁操作 + loader.ifInitialized(BigObject::destroy); + + Assert.assertTrue(loader.get().isDestroy); + } + + @Test + public void testOnLoadStaticFactoryMethod2() { + + LazyFunLoader loader = LazyFunLoader.onLoad(BigObject::new); + + // 若从未使用,则可以避免不必要的初始化 + loader.ifInitialized(it -> { + + Assert.fail(); + it.destroy(); + }); + + } } From 3e7dd16c4341e598a6e8bdc37590a79f55b04407 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 20 Mar 2022 20:46:32 +0800 Subject: [PATCH 02/13] fix BaseN --- CHANGELOG.md | 3 +- .../java/cn/hutool/core/codec/Base32.java | 214 +++++++---------- .../cn/hutool/core/codec/Base32Codec.java | 215 ++++++++++++++++++ .../cn/hutool/core/codec/Base58Codec.java | 195 ++++++++++------ .../cn/hutool/core/codec/Base64Decoder.java | 26 +-- .../java/cn/hutool/core/codec/PunyCode.java | 10 +- .../hutool/core/lang/mutable/MutableInt.java | 6 +- .../java/cn/hutool/core/codec/Base32Test.java | 19 ++ 8 files changed, 451 insertions(+), 237 deletions(-) create mode 100755 hutool-core/src/main/java/cn/hutool/core/codec/Base32Codec.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 469cfb88e..70f423188 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ * 【json 】 【可能兼容问题】修改JSONObject结构,继承自MapWrapper * 【core 】 【可能兼容问题】BeanCopier重构,新建XXXCopier,删除XXXValueProvider * 【core 】 【可能兼容问题】URLEncoder废弃,URLEncoderUtil使用RFC3986 -* 【core 】 【可能兼容问题】增加Base32.encode不足位数补= +* 【core 】 【可能兼容问题】Base32分离编码和解码,以便减少数据加载,支持Hex模式 +* 【core 】 【不兼容问题】PunyCode参数由String改为Charsequence ### 🐣新特性 * 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/Base32.java b/hutool-core/src/main/java/cn/hutool/core/codec/Base32.java index 1fa7db4cb..80c87b7e3 100644 --- a/hutool-core/src/main/java/cn/hutool/core/codec/Base32.java +++ b/hutool-core/src/main/java/cn/hutool/core/codec/Base32.java @@ -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 )
+ * Base32 - encodes and decodes RFC4648 Base32 (see https://datatracker.ietf.org/doc/html/rfc4648#section-6 )
* base32就是用32(2的5次方)个特定ASCII码来表示256个ASCII码。
- * 所以,5个ASCII字符经过base32编码后会变为8个字符(公约数为40),长度增加3/5.不足8n用“=”补足。 - * see http://blog.csdn.net/earbao/article/details/44453937 - * @author Looly + * 所以,5个ASCII字符经过base32编码后会变为8个字符(公约数为40),长度增加3/5.不足8n用“=”补足。
+ * 根据RFC4648 Base32规范,支持两种模式: + *
    + *
  • Base 32 Alphabet (ABCDEFGHIJKLMNOPQRSTUVWXYZ234567)
  • + *
  • "Extended Hex" Base 32 Alphabet (0123456789ABCDEFGHIJKLMNOPQRSTUV)
  • + *
* + * @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); + } } diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/Base32Codec.java b/hutool-core/src/main/java/cn/hutool/core/codec/Base32Codec.java new file mode 100755 index 000000000..42b926e54 --- /dev/null +++ b/hutool-core/src/main/java/cn/hutool/core/codec/Base32Codec.java @@ -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 )
+ * base32就是用32(2的5次方)个特定ASCII码来表示256个ASCII码。
+ * 所以,5个ASCII字符经过base32编码后会变为8个字符(公约数为40),长度增加3/5.不足8n用“=”补足。
+ * 根据RFC4648 Base32规范,支持两种模式: + *
    + *
  • Base 32 Alphabet (ABCDEFGHIJKLMNOPQRSTUVWXYZ234567)
  • + *
  • "Extended Hex" Base 32 Alphabet (0123456789ABCDEFGHIJKLMNOPQRSTUV)
  • + *
+ * + * @author Looly + * @since 5.8.0 + */ +public class Base32Codec implements Encoder, Decoder { + + 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 { + 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 { + 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; + } + } +} diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/Base58Codec.java b/hutool-core/src/main/java/cn/hutool/core/codec/Base58Codec.java index 6e6d02d82..da278731e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/codec/Base58Codec.java +++ b/hutool-core/src/main/java/cn/hutool/core/codec/Base58Codec.java @@ -15,10 +15,6 @@ public class Base58Codec implements Encoder, Decoder, Decoder= 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, Decoder { + 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 { + + 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); + } } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/Base64Decoder.java b/hutool-core/src/main/java/cn/hutool/core/codec/Base64Decoder.java index 20d0baad9..92b95cb33 100644 --- a/hutool-core/src/main/java/cn/hutool/core/codec/Base64Decoder.java +++ b/hutool-core/src/main/java/cn/hutool/core/codec/Base64Decoder.java @@ -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 } diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/PunyCode.java b/hutool-core/src/main/java/cn/hutool/core/codec/PunyCode.java index d49a046c4..fd3fc3864 100644 --- a/hutool-core/src/main/java/cn/hutool/core/codec/PunyCode.java +++ b/hutool-core/src/main/java/cn/hutool/core/codec/PunyCode.java @@ -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' * + * * @param d 输入字符 * @return 转换后的字符 * @throws UtilException 无效字符 @@ -242,6 +243,7 @@ public class PunyCode { * ... * '9' -> 35 * + * * @param c 输入字符 * @return 转换后的字符 * @throws UtilException 无效字符 diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableInt.java b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableInt.java index dce1dd9fc..ae033437f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableInt.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/mutable/MutableInt.java @@ -152,12 +152,12 @@ public class MutableInt extends Number implements Comparable, Mutabl * 相等需同时满足如下条件: *
    *
  1. 非空
  2. - *
  3. 类型为 {@link MutableInt}
  4. + *
  5. 类型为 MutableInt
  6. *
  7. 值相等
  8. *
* * @param obj 比对的对象 - * @return 相同返回true,否则 false + * @return 相同返回true,否则 {@code false} */ @Override public boolean equals(final Object obj) { @@ -176,7 +176,7 @@ public class MutableInt extends Number implements Comparable, Mutabl /** * 比较 * - * @param other 其它 {@link MutableInt} 对象 + * @param other 其它 MutableInt 对象 * @return x==y返回0,x<y返回-1,x>y返回1 */ @Override diff --git a/hutool-core/src/test/java/cn/hutool/core/codec/Base32Test.java b/hutool-core/src/test/java/cn/hutool/core/codec/Base32Test.java index a17f2e4ab..5d74654bf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/codec/Base32Test.java +++ b/hutool-core/src/test/java/cn/hutool/core/codec/Base32Test.java @@ -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 From 9b16947508df453bdcd0b79ffe4f3146b35ac5da Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 20 Mar 2022 21:20:51 +0800 Subject: [PATCH 03/13] add Base62 Inverted --- CHANGELOG.md | 6 +- .../java/cn/hutool/core/codec/Base62.java | 129 +++++++++++- .../cn/hutool/core/codec/Base62Codec.java | 190 +++++++++++------- .../java/cn/hutool/core/codec/Base62Test.java | 27 +++ 4 files changed, 272 insertions(+), 80 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70f423188..47b286e1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,14 @@ # 5.8.0 (2022-03-20) ### ❌不兼容特性 -* 【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分离编码和解码,以便减少数据加载,支持Hex模式 -* 【core 】 【不兼容问题】PunyCode参数由String改为Charsequence +* 【core 】 【可能兼容问题】Base58分离编码和解码 +* 【core 】 【可能兼容问题】Base62分离编码和解码,增加inverted模式支持 +* 【core 】 【兼容问题 】PunyCode参数由String改为Charsequence ### 🐣新特性 * 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/Base62.java b/hutool-core/src/main/java/cn/hutool/core/codec/Base62.java index bb0454540..9f8a458b4 100644 --- a/hutool-core/src/main/java/cn/hutool/core/codec/Base62.java +++ b/hutool-core/src/main/java/cn/hutool/core/codec/Base62.java @@ -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的编码和解码方案
* @@ -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); } } diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/Base62Codec.java b/hutool-core/src/main/java/cn/hutool/core/codec/Base62Codec.java index 1272406ac..055800dd7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/codec/Base62Codec.java +++ b/hutool-core/src/main/java/cn/hutool/core/codec/Base62Codec.java @@ -12,73 +12,13 @@ import java.io.Serializable; * @author Looly, Sebastian Ruhleder, sebastian@seruco.io * @since 4.5.9 */ -public class Base62Codec implements Encoder, Decoder, Serializable{ +public class Base62Codec implements Encoder, Decoder, 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, Decoder, Decoder { + /** + * GMP风格 + */ + private static final byte[] GMP = { // + '0', '1', '2', '3', '4', '5', '6', '7', // + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', // + 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', // + 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', // + 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', // + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', // + 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', // + 'u', 'v', 'w', 'x', 'y', 'z' // + }; + + /** + * 反转风格,即将GMP风格中的大小写做转换 + */ + private static final byte[] INVERTED = { // + '0', '1', '2', '3', '4', '5', '6', '7', // + '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', // + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', // + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', // + 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', // + 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', // + 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', // + 'U', 'V', 'W', 'X', 'Y', 'Z' // + }; + + 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 { + + 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, Decoder, Decoder Date: Sun, 20 Mar 2022 21:44:12 +0800 Subject: [PATCH 04/13] fix code --- hutool-core/src/main/java/cn/hutool/core/codec/Base64.java | 2 +- hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java b/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java index f59aa6cab..2ffc710c2 100644 --- a/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java +++ b/hutool-core/src/main/java/cn/hutool/core/codec/Base64.java @@ -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); } /** diff --git a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java index e6c916b6c..7625b9444 100644 --- a/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java +++ b/hutool-crypto/src/main/java/cn/hutool/crypto/digest/HMac.java @@ -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); } /** From be72eab9a0c2e3b35508b20773eb81556cf8b4c1 Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 20 Mar 2022 23:36:14 +0800 Subject: [PATCH 05/13] fix doc --- .../cn/hutool/cron/pattern/CronPattern.java | 3 +-- .../pattern/parser/DayOfWeekValueParser.java | 23 +++++++++++-------- .../cron/pattern/parser/HourValueParser.java | 9 ++++---- .../pattern/parser/MinuteValueParser.java | 9 ++++---- .../cron/pattern/parser/MonthValueParser.java | 15 +++++++----- .../pattern/parser/SecondValueParser.java | 7 +++--- .../pattern/parser/SimpleValueParser.java | 21 ++++++++++------- .../cron/pattern/parser/YearValueParser.java | 9 ++++---- 8 files changed, 55 insertions(+), 41 deletions(-) diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java index b026cdd3c..4b57f8c8a 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java @@ -152,7 +152,6 @@ public class CronPattern { * @return 如果匹配返回 {@code true}, 否则返回 {@code false} */ public boolean match(GregorianCalendar calendar, boolean isMatchSecond) { - final int second = calendar.get(Calendar.SECOND); 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); @@ -162,7 +161,7 @@ public class CronPattern { boolean eval; for (int i = 0; i < matcherSize; i++) { - eval = ((false == isMatchSecond) || secondMatchers.get(i).match(second)) // 匹配秒(非秒匹配模式下始终返回true) + eval = ((false == isMatchSecond) || secondMatchers.get(i).match(calendar.get(Calendar.SECOND))) // 匹配秒(非秒匹配模式下始终返回true) && minuteMatchers.get(i).match(minute)// 匹配分 && hourMatchers.get(i).match(hour)// 匹配时 && isMatchDayOfMonth(dayOfMonthMatchers.get(i), dayOfMonth, month, calendar.isLeapYear(year))// 匹配日 diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/DayOfWeekValueParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/DayOfWeekValueParser.java index bad5fa9fc..bf486c31c 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/DayOfWeekValueParser.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/DayOfWeekValueParser.java @@ -4,20 +4,22 @@ import cn.hutool.cron.CronException; /** * 星期值处理
- * 1表示星期一,2表示星期二,依次类推,0和7都可以表示星期日 - * - * @author Looly + * 1表示星期一,2表示星期二,依次类推,0和7都可以表示星期日
+ * {@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 SimpleValueParser { + + /** + * Weeks aliases. + */ + private static final String[] ALIASES = {"sun", "mon", "tue", "wed", "thu", "fri", "sat"}; public DayOfWeekValueParser() { super(0, 7); } - + /** * 对于星期提供转换
* 1表示星期一,2表示星期二,依次类推,0和7都可以表示星期日 @@ -33,16 +35,17 @@ public class DayOfWeekValueParser extends SimpleValueParser { /** * 解析别名 + * * @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; diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/HourValueParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/HourValueParser.java index 5041f3837..ad3c530cb 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/HourValueParser.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/HourValueParser.java @@ -1,12 +1,13 @@ package cn.hutool.cron.pattern.parser; /** - * 小时值处理 - * @author Looly + * 小时值处理
+ * 小时被限定在0-23 * + * @author Looly */ -public class HourValueParser extends SimpleValueParser{ - +public class HourValueParser extends SimpleValueParser { + public HourValueParser() { super(0, 23); } diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/MinuteValueParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/MinuteValueParser.java index 474660477..28fda7468 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/MinuteValueParser.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/MinuteValueParser.java @@ -1,12 +1,13 @@ package cn.hutool.cron.pattern.parser; /** - * 分钟值处理 - * @author Looly + * 分钟值处理
+ * 限定于0-59 * + * @author Looly */ -public class MinuteValueParser extends SimpleValueParser{ - +public class MinuteValueParser extends SimpleValueParser { + public MinuteValueParser() { super(0, 59); } diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/MonthValueParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/MonthValueParser.java index 2f2f47d52..ab870a8d1 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/MonthValueParser.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/MonthValueParser.java @@ -3,20 +3,22 @@ package cn.hutool.cron.pattern.parser; import cn.hutool.cron.CronException; /** - * 月份值处理 - * - * @author Looly + * 月份值处理
+ * 限定于1-12,1表示一月,支持别名,如一月是{@code jan} * + * @author Looly */ public class MonthValueParser extends SimpleValueParser { - /** 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 { @@ -28,6 +30,7 @@ public class MonthValueParser extends SimpleValueParser { /** * 解析别名 + * * @param value 别名值 * @return 月份int值 * @throws CronException 无效月别名抛出此异常 diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/SecondValueParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/SecondValueParser.java index b3697ccbc..ae0839c49 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/SecondValueParser.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/SecondValueParser.java @@ -1,9 +1,10 @@ package cn.hutool.cron.pattern.parser; /** - * 秒值处理 - * @author Looly + * 秒值处理
+ * 限定于0-59 * + * @author Looly */ -public class SecondValueParser extends MinuteValueParser{ +public class SecondValueParser extends MinuteValueParser { } diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/SimpleValueParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/SimpleValueParser.java index 280e92290..7a3d139e9 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/SimpleValueParser.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/SimpleValueParser.java @@ -3,15 +3,20 @@ package cn.hutool.cron.pattern.parser; import cn.hutool.cron.CronException; /** - * 简易值转换器。将给定String值转为int - * @author Looly + * 简易值转换器。将给定String值转为int,并限定最大值和最小值
+ * 此类同时识别{@code L} 为最大值。 * + * @author Looly */ public class SimpleValueParser implements ValueParser { - - /** 最小值(包括) */ + + /** + * 最小值(包括) + */ protected int min; - /** 最大值(包括) */ + /** + * 最大值(包括) + */ protected int max; /** @@ -21,10 +26,10 @@ public class SimpleValueParser implements ValueParser { * @param max 最大值(包括) */ public SimpleValueParser(int min, int max) { - if(min > max){ + if (min > max) { this.min = max; this.max = min; - }else{ + } else { this.min = min; this.max = max; } @@ -32,7 +37,7 @@ public class SimpleValueParser implements ValueParser { @Override public int parse(String value) throws CronException { - if("L".equalsIgnoreCase(value)){ + if ("L".equalsIgnoreCase(value)) { // L表示最大值 return max; } diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/YearValueParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/YearValueParser.java index 13222c3e6..858d020dd 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/YearValueParser.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/YearValueParser.java @@ -1,12 +1,13 @@ package cn.hutool.cron.pattern.parser; /** - * 年值处理 - * @author Looly + * 年值处理
+ * 年的限定在1970-2099年 * + * @author Looly */ -public class YearValueParser extends SimpleValueParser{ - +public class YearValueParser extends SimpleValueParser { + public YearValueParser() { super(1970, 2099); } From 4fbda4565a275709089cdf0d6b889f51b5221091 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 21 Mar 2022 00:42:01 +0800 Subject: [PATCH 06/13] add class --- CHANGELOG.md | 1 + .../java/cn/hutool/core/date/DateUtil.java | 2 +- .../cn/hutool/cron/pattern/CronPattern.java | 193 ++---------------- .../cron/pattern/matcher/MatcherTable.java | 119 +++++++++++ .../pattern/parser/CronPatternParser.java | 132 ++++++++++++ .../cron/pattern/parser/MonthValueParser.java | 2 +- 6 files changed, 270 insertions(+), 179 deletions(-) create mode 100644 hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/MatcherTable.java create mode 100644 hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/CronPatternParser.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 47b286e1e..bf4f4ca78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,7 @@ * 【poi 】 优化ExcelBase,将alias放入 * 【poi 】 优化ExcelBase,将alias放入 * 【core 】 改进StrUtil#startWith、endWith性能 +* 【cron 】 增加CronPatternParser、MatcherTable ### 🐞Bug修复 * 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java index 6df40341a..ab642f7d5 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/DateUtil.java @@ -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); } /** diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java index 4b57f8c8a..ce05317de 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/CronPattern.java @@ -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; *
  • :范围:1~12,同时支持不区分大小写的别名:"jan","feb", "mar", "apr", "may","jun", "jul", "aug", "sep","oct", "nov", "dec"
  • *
  • :范围:0 (Sunday)~6(Saturday),7也可以表示周日,同时支持不区分大小写的别名:"sun","mon", "tue", "wed", "thu","fri", "sat","L" 表示周六
  • * - * + *

    * 为了兼容Quartz表达式,同时支持6位和7位表达式,其中:
    * *

      * 当为6位时,第一位表示 ,范围0~59,但是第一位不做匹配
      * 当为7位时,最后一位表示 ,范围1970~2099,但是第7位不做解析,也不做匹配
      * 
    - * + *

    * 当定时任务运行到的时间匹配这些表达式后,任务被启动。
    * 注意: * @@ -47,7 +32,7 @@ import java.util.TimeZone; * 当isMatchSecond为{@code true}时才会匹配秒部分 * 默认都是关闭的 * - * + *

    * 对于每一个子表达式,同样支持以下形式: *

      *
    • * :表示匹配这个位置所有的时间
    • @@ -62,10 +47,10 @@ import java.util.TimeZone; *
        * 间隔(/) > 区间(-) > 列表(,)
        * 
      - * + *

      * 例如 2,3,6/3中,由于“/”优先级高,因此相当于2,3,(6/3),结果与 2,3,6等价
      *
      - * + *

      * 一些例子: *

        *
      • 5 * * * * :每个点钟的5分执行,00:05,01:05……
      • @@ -77,36 +62,11 @@ import java.util.TimeZone; *
      * * @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 secondMatchers = new ArrayList<>(); - /** 分字段匹配列表 */ - private final List minuteMatchers = new ArrayList<>(); - /** 时字段匹配列表 */ - private final List hourMatchers = new ArrayList<>(); - /** 每月几号字段匹配列表 */ - private final List dayOfMonthMatchers = new ArrayList<>(); - /** 月字段匹配列表 */ - private final List monthMatchers = new ArrayList<>(); - /** 星期字段匹配列表 */ - private final List dayOfWeekMatchers = new ArrayList<>(); - /** 年字段匹配列表 */ - private final List 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,11 +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 = 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); @@ -159,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(calendar.get(Calendar.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 @@ -180,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 matchers, int index, int value) { - return (matchers.size() <= index) || matchers.get(index).match(value); - } - - /** - * 解析复合任务表达式 - * - * @param groupPattern 复合表达式 - */ - private void parseGroupPattern(String groupPattern) { - List 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 } diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/MatcherTable.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/MatcherTable.java new file mode 100644 index 000000000..9dc20ff02 --- /dev/null +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/MatcherTable.java @@ -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 secondMatchers; + /** + * 分字段匹配列表 + */ + public final List minuteMatchers; + /** + * 时字段匹配列表 + */ + public final List hourMatchers; + /** + * 每月几号字段匹配列表 + */ + public final List dayOfMonthMatchers; + /** + * 月字段匹配列表 + */ + public final List monthMatchers; + /** + * 星期字段匹配列表 + */ + public final List dayOfWeekMatchers; + /** + * 年字段匹配列表 + */ + public final List 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 matchers, int index, int value) { + return (matchers.size() <= index) || matchers.get(index).match(value); + } +} diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/CronPatternParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/CronPatternParser.java new file mode 100644 index 000000000..e85e20dee --- /dev/null +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/CronPatternParser.java @@ -0,0 +1,132 @@ +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 cn.hutool.cron.pattern.matcher.ValueMatcherBuilder; + +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; + } + + /** + * 解析复合任务表达式,格式为: + *
      +	 *     cronA | cronB | ...
      +	 * 
      + * + * @param groupPattern 复合表达式 + */ + private void parseGroupPattern(String groupPattern) { + final List 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); + } + + // 秒 + if (1 == offset) {// 支持秒的表达式 + try { + matcherTable.secondMatchers.add(ValueMatcherBuilder.build(parts[0], SECOND_VALUE_PARSER)); + } catch (Exception e) { + throw new CronException(e, "Invalid pattern [{}], parsing 'second' field error!", pattern); + } + } else {// 不支持秒的表达式,则第一位按照表达式生成时间的秒数赋值,表示整分匹配 + matcherTable.secondMatchers.add(ValueMatcherBuilder.build(String.valueOf(DateUtil.date().second()), SECOND_VALUE_PARSER)); + } + // 分 + try { + matcherTable.minuteMatchers.add(ValueMatcherBuilder.build(parts[offset], MINUTE_VALUE_PARSER)); + } catch (Exception e) { + throw new CronException(e, "Invalid pattern [{}], parsing 'minute' field error!", pattern); + } + // 小时 + try { + matcherTable.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 { + matcherTable.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 { + matcherTable.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 { + matcherTable.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 { + matcherTable.yearMatchers.add(ValueMatcherBuilder.build(parts[6], YEAR_VALUE_PARSER)); + } catch (Exception e) { + throw new CronException(e, "Invalid pattern [{}], parsing 'year' field error!", pattern); + } + } else {// 不支持年的表达式,全部匹配 + matcherTable.yearMatchers.add(new AlwaysTrueValueMatcher()); + } + } +} diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/MonthValueParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/MonthValueParser.java index ab870a8d1..3a85d72e0 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/MonthValueParser.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/MonthValueParser.java @@ -4,7 +4,7 @@ import cn.hutool.cron.CronException; /** * 月份值处理
      - * 限定于1-12,1表示一月,支持别名,如一月是{@code jan} + * 限定于1-12,1表示一月,支持别名(忽略大小写),如一月是{@code jan} * * @author Looly */ From 8718e6dfe145554a4a8ba562013829721c37f0fb Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 21 Mar 2022 01:55:49 +0800 Subject: [PATCH 07/13] add methods --- CHANGELOG.md | 1 + .../AbsValueParser.java} | 153 ++++++++++++------ .../pattern/parser/CronPatternParser.java | 72 ++++----- .../pattern/parser/DayOfMonthValueParser.java | 27 +++- .../pattern/parser/DayOfWeekValueParser.java | 12 +- .../cron/pattern/parser/HourValueParser.java | 13 +- .../pattern/parser/MinuteValueParser.java | 16 +- .../cron/pattern/parser/MonthValueParser.java | 12 +- .../pattern/parser/SecondValueParser.java | 12 ++ .../pattern/parser/SimpleValueParser.java | 66 -------- .../cron/pattern/parser/ValueParser.java | 31 +++- .../cron/pattern/parser/YearValueParser.java | 23 ++- 12 files changed, 269 insertions(+), 169 deletions(-) rename hutool-cron/src/main/java/cn/hutool/cron/pattern/{matcher/ValueMatcherBuilder.java => parser/AbsValueParser.java} (58%) delete mode 100644 hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/SimpleValueParser.java diff --git a/CHANGELOG.md b/CHANGELOG.md index bf4f4ca78..24a0d664b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * 【core 】 【可能兼容问题】Base58分离编码和解码 * 【core 】 【可能兼容问题】Base62分离编码和解码,增加inverted模式支持 * 【core 】 【兼容问题 】PunyCode参数由String改为Charsequence +* 【cron 】 【可能兼容问题】SimpleValueParser改名为AbsValueParser,改为abstract ### 🐣新特性 * 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee) diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/ValueMatcherBuilder.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/AbsValueParser.java similarity index 58% rename from hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/ValueMatcherBuilder.java rename to hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/AbsValueParser.java index 4cc06ccb0..a436e78de 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/ValueMatcherBuilder.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/AbsValueParser.java @@ -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,并限定最大值和最小值
      + * 此类同时识别{@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; + } + /** * 处理定时任务表达式每个时间字段
      * 多个时间使用逗号分隔 - * + * * @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 values = parseArray(value, parser); + List 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}
      + * 默认为{@link BoolArrayValueMatcher},如果有特殊实现,子类须重写此方法 + * + * @param values 数字值列表 + * @return {@link ValueMatcher} + */ + protected ValueMatcher buildValueMatcher(List values){ + return new BoolArrayValueMatcher(values); + } + /** * 处理数组形式表达式
      * 处理的形式包括: @@ -56,15 +114,14 @@ public class ValueMatcherBuilder { *
    • a,b,c,d
    • *
    * @param value 子表达式值 - * @param parser 针对这个字段的解析器 * @return 值列表 */ - private static List parseArray(String value, ValueParser parser){ + private List parseArray(String value){ final List values = new ArrayList<>(); - + final List 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 { *
  • a/b*/b
  • *
  • a-b/2
  • * - * + * * @param value 表达式值 - * @param parser 针对这个时间字段的解析器 * @return List */ - private static List parseStep(String value, ValueParser parser) { + private List parseStep(String value) { final List parts = StrUtil.split(value, StrUtil.C_SLASH); int size = parts.size(); - + List 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 { *
  • 8-3
  • *
  • 3-3
  • * - * + * * @param value 范围表达式 * @param step 步进 - * @param parser 针对这个时间字段的解析器 * @return List */ - private static List parseRange(String value, int step, ValueParser parser) { + private List parseRange(String value, int step) { final List 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 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; } - + /** * 是否为全匹配符
    * 全匹配符指 * 或者 ? - * + * * @param value 被检查的值 * @return 是否为全匹配符 * @since 4.1.18 diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/CronPatternParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/CronPatternParser.java index e85e20dee..052fb42cf 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/CronPatternParser.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/CronPatternParser.java @@ -5,7 +5,6 @@ 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 cn.hutool.cron.pattern.matcher.ValueMatcherBuilder; import java.util.List; @@ -78,55 +77,40 @@ public class CronPatternParser { throw new CronException("Pattern [{}] is invalid, it must be 5-7 parts!", pattern); } - // 秒 - if (1 == offset) {// 支持秒的表达式 - try { - matcherTable.secondMatchers.add(ValueMatcherBuilder.build(parts[0], SECOND_VALUE_PARSER)); - } catch (Exception e) { - throw new CronException(e, "Invalid pattern [{}], parsing 'second' field error!", pattern); - } - } else {// 不支持秒的表达式,则第一位按照表达式生成时间的秒数赋值,表示整分匹配 - matcherTable.secondMatchers.add(ValueMatcherBuilder.build(String.valueOf(DateUtil.date().second()), SECOND_VALUE_PARSER)); - } + // 秒,如果不支持秒的表达式,则第一位按照表达式生成时间的秒数赋值,表示整分匹配 + final String secondPart = (1 == offset) ? parts[0] : String.valueOf(DateUtil.date().second()); + parseToTable(SECOND_VALUE_PARSER, secondPart); + // 分 - try { - matcherTable.minuteMatchers.add(ValueMatcherBuilder.build(parts[offset], MINUTE_VALUE_PARSER)); - } catch (Exception e) { - throw new CronException(e, "Invalid pattern [{}], parsing 'minute' field error!", pattern); - } - // 小时 - try { - matcherTable.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 { - matcherTable.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); - } + parseToTable(MINUTE_VALUE_PARSER, parts[offset]); + + // 时 + parseToTable(HOUR_VALUE_PARSER, parts[1 + offset]); + + // 天 + parseToTable(DAY_OF_MONTH_VALUE_PARSER, parts[2 + offset]); + // 月 - try { - matcherTable.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 { - matcherTable.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); - } + parseToTable(MONTH_VALUE_PARSER, parts[3 + offset]); + + // 周 + parseToTable(DAY_OF_WEEK_VALUE_PARSER, parts[4 + offset]); + // 年 if (parts.length == 7) {// 支持年的表达式 - try { - matcherTable.yearMatchers.add(ValueMatcherBuilder.build(parts[6], YEAR_VALUE_PARSER)); - } catch (Exception e) { - throw new CronException(e, "Invalid pattern [{}], parsing 'year' field error!", pattern); - } + 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); + } } diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/DayOfMonthValueParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/DayOfMonthValueParser.java index 2cac2a8b5..e3ddea83e 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/DayOfMonthValueParser.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/DayOfMonthValueParser.java @@ -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; /** * 每月的几号值处理
    * 每月最多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 values) { + //考虑每月的天数不同,且存在闰年情况,日匹配单独使用 + return new DayOfMonthValueMatcher(values); + } } diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/DayOfWeekValueParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/DayOfWeekValueParser.java index bf486c31c..8b608db44 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/DayOfWeekValueParser.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/DayOfWeekValueParser.java @@ -1,6 +1,7 @@ package cn.hutool.cron.pattern.parser; import cn.hutool.cron.CronException; +import cn.hutool.cron.pattern.matcher.MatcherTable; /** * 星期值处理
    @@ -9,7 +10,7 @@ import cn.hutool.cron.CronException; * * @author Looly */ -public class DayOfWeekValueParser extends SimpleValueParser { +public class DayOfWeekValueParser extends AbsValueParser { /** * Weeks aliases. @@ -33,6 +34,15 @@ 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); + } + } + /** * 解析别名 * diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/HourValueParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/HourValueParser.java index ad3c530cb..bc7b5159c 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/HourValueParser.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/HourValueParser.java @@ -1,15 +1,26 @@ package cn.hutool.cron.pattern.parser; +import cn.hutool.cron.CronException; +import cn.hutool.cron.pattern.matcher.MatcherTable; + /** * 小时值处理
    * 小时被限定在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); + } + } } diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/MinuteValueParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/MinuteValueParser.java index 28fda7468..f2f76768a 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/MinuteValueParser.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/MinuteValueParser.java @@ -1,15 +1,29 @@ package cn.hutool.cron.pattern.parser; +import cn.hutool.cron.CronException; +import cn.hutool.cron.pattern.matcher.MatcherTable; + /** * 分钟值处理
    * 限定于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); + } + } } diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/MonthValueParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/MonthValueParser.java index 3a85d72e0..e58a62bb9 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/MonthValueParser.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/MonthValueParser.java @@ -1,6 +1,7 @@ package cn.hutool.cron.pattern.parser; import cn.hutool.cron.CronException; +import cn.hutool.cron.pattern.matcher.MatcherTable; /** * 月份值处理
    @@ -8,7 +9,7 @@ import cn.hutool.cron.CronException; * * @author Looly */ -public class MonthValueParser extends SimpleValueParser { +public class MonthValueParser extends AbsValueParser { /** * Months aliases. @@ -28,6 +29,15 @@ 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); + } + } + /** * 解析别名 * diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/SecondValueParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/SecondValueParser.java index ae0839c49..084157a81 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/SecondValueParser.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/SecondValueParser.java @@ -1,5 +1,8 @@ package cn.hutool.cron.pattern.parser; +import cn.hutool.cron.CronException; +import cn.hutool.cron.pattern.matcher.MatcherTable; + /** * 秒值处理
    * 限定于0-59 @@ -7,4 +10,13 @@ package cn.hutool.cron.pattern.parser; * @author Looly */ 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); + } + } } diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/SimpleValueParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/SimpleValueParser.java deleted file mode 100644 index 7a3d139e9..000000000 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/SimpleValueParser.java +++ /dev/null @@ -1,66 +0,0 @@ -package cn.hutool.cron.pattern.parser; - -import cn.hutool.cron.CronException; - -/** - * 简易值转换器。将给定String值转为int,并限定最大值和最小值
    - * 此类同时识别{@code L} 为最大值。 - * - * @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; - } -} diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/ValueParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/ValueParser.java index 97c62fbf1..2e49f624f 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/ValueParser.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/ValueParser.java @@ -1,5 +1,8 @@ package cn.hutool.cron.pattern.parser; +import cn.hutool.cron.pattern.matcher.MatcherTable; +import cn.hutool.cron.pattern.matcher.ValueMatcher; + /** * 值处理接口
    * 值处理用于限定表达式中相应位置的值范围,并转换表达式值为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},支持的表达式包括: + *
      + *
    • 单值或通配符形式,如 a*
    • + *
    • 数组形式,如 1,2,3
    • + *
    • 间隔形式,如 a/b*/b
    • + *
    • 范围形式,如 3-8
    • + *
    + * + * @param pattern 对应时间部分的表达式 + * @return {@link ValueMatcher} + */ + ValueMatcher parseAsValueMatcher(String pattern); + /** * 处理String值并转为int
    * 转换包括: @@ -15,7 +40,7 @@ public interface ValueParser { *
  • 数字字符串转为数字
  • *
  • 别名转为对应的数字(如月份和星期)
  • * - * + * * @param value String值 * @return int */ @@ -23,14 +48,14 @@ public interface ValueParser { /** * 返回最小值 - * + * * @return 最小值 */ int getMin(); /** * 返回最大值 - * + * * @return 最大值 */ int getMax(); diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/YearValueParser.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/YearValueParser.java index 858d020dd..484a1cf4b 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/YearValueParser.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/parser/YearValueParser.java @@ -1,15 +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; + /** * 年值处理
    * 年的限定在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 values) { + //考虑年数字太大,不适合boolean数组,单独使用列表遍历匹配 + return new YearValueMatcher(values); + } } From e99ce6805ce736595a5a5bca32e5b745af06a304 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 21 Mar 2022 02:05:15 +0800 Subject: [PATCH 08/13] fix doc --- .../src/main/java/cn/hutool/core/text/AntPathMatcher.java | 1 + 1 file changed, 1 insertion(+) diff --git a/hutool-core/src/main/java/cn/hutool/core/text/AntPathMatcher.java b/hutool-core/src/main/java/cn/hutool/core/text/AntPathMatcher.java index aabc3e97b..8d0850772 100755 --- a/hutool-core/src/main/java/cn/hutool/core/text/AntPathMatcher.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/AntPathMatcher.java @@ -137,6 +137,7 @@ public class AntPathMatcher { * * @param cachePatterns 是否缓存表达式 * @see #getStringMatcher(String) + * @return this */ public AntPathMatcher setCachePatterns(boolean cachePatterns) { this.cachePatterns = cachePatterns; From 7050927b0a56485bdd83c597849586e39af5b6fe Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 21 Mar 2022 13:33:39 +0800 Subject: [PATCH 09/13] fix bug --- CHANGELOG.md | 4 +++- .../src/main/java/cn/hutool/poi/excel/ExcelUtil.java | 4 ++-- .../cn/hutool/poi/excel/sax/Excel07SaxReader.java | 11 ++++------- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24a0d664b..caed37874 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.8.0 (2022-03-20) +# 5.8.0 (2022-03-21) ### ❌不兼容特性 * 【db 】 【不向下兼容 】增加MongoDB4.x支持返回MongoClient变更(pr#568@Gitee) @@ -14,6 +14,7 @@ * 【core 】 【可能兼容问题】Base62分离编码和解码,增加inverted模式支持 * 【core 】 【兼容问题 】PunyCode参数由String改为Charsequence * 【cron 】 【可能兼容问题】SimpleValueParser改名为AbsValueParser,改为abstract +* 【poi 】 【可能兼容问题】ExcelUtil.getBigWriter返回值改为BigExcelWriter ### 🐣新特性 * 【http 】 HttpRequest.form采用TableMap方式(issue#I4W427@Gitee) @@ -46,6 +47,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) diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelUtil.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelUtil.java index 17860c8ae..8db7e1ced 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelUtil.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/ExcelUtil.java @@ -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) { diff --git a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java index b50034cb3..2c4ec4b71 100644 --- a/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java +++ b/hutool-poi/src/main/java/cn/hutool/poi/excel/sax/Excel07SaxReader.java @@ -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 { } // 获取共享字符串表 - 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); } From 8974fe57839b30aee94c8155ea181a9b62e867a7 Mon Sep 17 00:00:00 2001 From: VampireAchao Date: Mon, 21 Mar 2022 14:19:43 +0800 Subject: [PATCH 10/13] =?UTF-8?q?Opt.ofEmptyAble=E4=BB=8EList=E6=8B=93?= =?UTF-8?q?=E5=B1=95=E4=B8=BACollection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hutool-core/src/main/java/cn/hutool/core/lang/Opt.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java b/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java index dad1b5cd0..fcc4d1951 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java @@ -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 { * @return 一个包裹里元素可能为空的 {@code Opt} * @since 5.7.17 */ - public static Opt> ofEmptyAble(List value) { + public static > Opt ofEmptyAble(R value) { return CollectionUtil.isEmpty(value) ? empty() : new Opt<>(value); } From c88cbf5d1be9fd4eed43fc4d40156c0c464ceeb6 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 21 Mar 2022 22:22:25 +0800 Subject: [PATCH 11/13] change method --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index caed37874..d63d17cad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * 【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) From 2ac275dd8fa2d81c70b1f224ec0fc7e31ce47637 Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 22 Mar 2022 10:16:06 +0800 Subject: [PATCH 12/13] add sys props --- CHANGELOG.md | 3 ++- .../main/java/cn/hutool/core/lang/loader/LazyFunLoader.java | 5 +++-- .../java/cn/hutool/core/lang/loader/LazyFunLoaderTest.java | 4 ++-- hutool-http/src/main/java/cn/hutool/http/GlobalHeaders.java | 6 +++++- 4 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d63d17cad..0a5619315 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ # 🚀Changelog ------------------------------------------------------------------------------------------------------------- -# 5.8.0 (2022-03-21) +# 5.8.0 (2022-03-22) ### ❌不兼容特性 * 【db 】 【不向下兼容 】增加MongoDB4.x支持返回MongoClient变更(pr#568@Gitee) @@ -37,6 +37,7 @@ * 【poi 】 优化ExcelBase,将alias放入 * 【core 】 改进StrUtil#startWith、endWith性能 * 【cron 】 增加CronPatternParser、MatcherTable +* 【http 】 GlobalHeaders增加系统属性allowUnsafeServerCertChange、allowUnsafeRenegotiation ### 🐞Bug修复 * 【core 】 修复ObjectUtil.hasNull传入null返回true的问题(pr#555@Gitee) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/loader/LazyFunLoader.java b/hutool-core/src/main/java/cn/hutool/core/lang/loader/LazyFunLoader.java index 3cc626a6f..df112f2f0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/loader/LazyFunLoader.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/loader/LazyFunLoader.java @@ -29,9 +29,10 @@ public class LazyFunLoader extends LazyLoader { * @param supplier 用于生成对象的函数 * @param 对象类型 * @return 函数式懒加载加载器对象 + * @since 5.8.0 */ - public static LazyFunLoader onLoad(final Supplier supplier) { - Assert.notNull(supplier, "supplier"); + public static LazyFunLoader on(final Supplier supplier) { + Assert.notNull(supplier, "supplier must be not null!"); return new LazyFunLoader<>(supplier); } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/loader/LazyFunLoaderTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/loader/LazyFunLoaderTest.java index 0e35bf717..f8d9888cb 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/loader/LazyFunLoaderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/loader/LazyFunLoaderTest.java @@ -46,7 +46,7 @@ public class LazyFunLoaderTest { @Test public void testOnLoadStaticFactoryMethod1() { - LazyFunLoader loader = LazyFunLoader.onLoad(BigObject::new); + LazyFunLoader loader = LazyFunLoader.on(BigObject::new); Assert.assertNotNull(loader.get()); Assert.assertTrue(loader.isInitialize()); @@ -60,7 +60,7 @@ public class LazyFunLoaderTest { @Test public void testOnLoadStaticFactoryMethod2() { - LazyFunLoader loader = LazyFunLoader.onLoad(BigObject::new); + LazyFunLoader loader = LazyFunLoader.on(BigObject::new); // 若从未使用,则可以避免不必要的初始化 loader.ifInitialized(it -> { diff --git a/hutool-http/src/main/java/cn/hutool/http/GlobalHeaders.java b/hutool-http/src/main/java/cn/hutool/http/GlobalHeaders.java index 44c12c40a..3dabe6288 100644 --- a/hutool-http/src/main/java/cn/hutool/http/GlobalHeaders.java +++ b/hutool-http/src/main/java/cn/hutool/http/GlobalHeaders.java @@ -23,7 +23,7 @@ public enum GlobalHeaders { /** * 存储头信息 */ - Map> headers = new HashMap<>(); + final Map> 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(); } From 8d18dbf7a5178d43b5830f3e365cdc9fa61e201d Mon Sep 17 00:00:00 2001 From: Looly Date: Tue, 22 Mar 2022 13:23:32 +0800 Subject: [PATCH 13/13] add method --- .../matcher/BoolArrayValueMatcher.java | 36 +++++++++++++++---- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/BoolArrayValueMatcher.java b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/BoolArrayValueMatcher.java index 4ffff81e1..a95fb3b67 100644 --- a/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/BoolArrayValueMatcher.java +++ b/hutool-cron/src/main/java/cn/hutool/cron/pattern/matcher/BoolArrayValueMatcher.java @@ -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 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});